template
This commit is contained in:
parent
dcae960928
commit
a7e6ed2e4b
3 changed files with 107 additions and 14 deletions
|
|
@ -169,24 +169,31 @@ async def _debug_token(db: Session, token: str) -> dict[str, Any]:
|
||||||
return payload if isinstance(payload, dict) else {}
|
return payload if isinstance(payload, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
def _waba_ids_from_debug(debug: dict[str, Any]) -> list[str]:
|
def _target_ids(debug: dict[str, Any], scope: str) -> list[str]:
|
||||||
found: list[str] = []
|
found: list[str] = []
|
||||||
scopes = debug.get("granular_scopes")
|
scopes = debug.get("granular_scopes")
|
||||||
if not isinstance(scopes, list):
|
if not isinstance(scopes, list):
|
||||||
return found
|
return found
|
||||||
preferred = {"whatsapp_business_management", "whatsapp_business_messaging"}
|
|
||||||
for item in scopes:
|
for item in scopes:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict) or item.get("scope") != scope:
|
||||||
continue
|
|
||||||
if item.get("scope") not in preferred:
|
|
||||||
continue
|
continue
|
||||||
for target in item.get("target_ids") or []:
|
for target in item.get("target_ids") or []:
|
||||||
value = str(target)
|
value = str(target).strip()
|
||||||
if value and value not in found:
|
if value and value not in found:
|
||||||
found.append(value)
|
found.append(value)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def _waba_ids_from_debug(debug: dict[str, Any]) -> list[str]:
|
||||||
|
"""whatsapp_business_management targets are WABA IDs."""
|
||||||
|
return _target_ids(debug, "whatsapp_business_management")
|
||||||
|
|
||||||
|
|
||||||
|
def _phone_ids_from_debug(debug: dict[str, Any]) -> list[str]:
|
||||||
|
"""whatsapp_business_messaging targets are phone number IDs, not WABA IDs."""
|
||||||
|
return _target_ids(debug, "whatsapp_business_messaging")
|
||||||
|
|
||||||
|
|
||||||
def _as_uuid(account_id: Any) -> uuid.UUID:
|
def _as_uuid(account_id: Any) -> uuid.UUID:
|
||||||
try:
|
try:
|
||||||
return account_id if isinstance(account_id, uuid.UUID) else uuid.UUID(str(account_id))
|
return account_id if isinstance(account_id, uuid.UUID) else uuid.UUID(str(account_id))
|
||||||
|
|
@ -599,7 +606,15 @@ async def complete_signup(db: Session, user: UserAccount, payload: dict[str, Any
|
||||||
}
|
}
|
||||||
|
|
||||||
session_waba = str(payload.get("waba_id") or "").strip()
|
session_waba = str(payload.get("waba_id") or "").strip()
|
||||||
waba_ids = [session_waba] if session_waba else _waba_ids_from_debug(debug)
|
session_phone = str(payload.get("phone_number_id") or "").strip() or None
|
||||||
|
management_ids = _waba_ids_from_debug(debug)
|
||||||
|
phone_ids = _phone_ids_from_debug(debug)
|
||||||
|
if session_waba and session_waba in phone_ids and session_waba not in management_ids:
|
||||||
|
session_phone = session_phone or session_waba
|
||||||
|
session_waba = ""
|
||||||
|
waba_ids = [session_waba] if session_waba else management_ids
|
||||||
|
if not session_phone and len(phone_ids) == 1:
|
||||||
|
session_phone = phone_ids[0]
|
||||||
if not waba_ids:
|
if not waba_ids:
|
||||||
_audit(db, user, "provision_failed", "Embedded Signup did not return a WhatsApp Business Account")
|
_audit(db, user, "provision_failed", "Embedded Signup did not return a WhatsApp Business Account")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
@ -622,7 +637,6 @@ async def complete_signup(db: Session, user: UserAccount, payload: dict[str, Any
|
||||||
db.flush()
|
db.flush()
|
||||||
_audit(db, user, "authorization_received", "Embedded Signup returned an authorization code", connection_id=connection.id)
|
_audit(db, user, "authorization_received", "Embedded Signup returned an authorization code", connection_id=connection.id)
|
||||||
|
|
||||||
session_phone = str(payload.get("phone_number_id") or "").strip() or None
|
|
||||||
accounts: list[WhatsAppBusinessAccount] = []
|
accounts: list[WhatsAppBusinessAccount] = []
|
||||||
for waba_id in waba_ids:
|
for waba_id in waba_ids:
|
||||||
accounts.append(await _provision_waba(db, user, connection, token, waba_id, session_phone))
|
accounts.append(await _provision_waba(db, user, connection, token, waba_id, session_phone))
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,19 @@ from sqlalchemy.orm import Session, selectinload
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
from .models import UserAccount, WhatsAppBusinessAccount, WhatsAppMessage, WhatsAppMessageTemplate, WhatsAppMetaConnection
|
from .models import UserAccount, WhatsAppBusinessAccount, WhatsAppMessage, WhatsAppMessageTemplate, WhatsAppMetaConnection
|
||||||
from .whatsapp_onboarding import _graph, _graph_error_message, _owned_account, _quality_label, _token_app_id
|
from .whatsapp_onboarding import (
|
||||||
|
_apply_phones,
|
||||||
|
_debug_token,
|
||||||
|
_graph,
|
||||||
|
_graph_error_message,
|
||||||
|
_load_phones,
|
||||||
|
_load_waba,
|
||||||
|
_owned_account,
|
||||||
|
_phone_ids_from_debug,
|
||||||
|
_quality_label,
|
||||||
|
_token_app_id,
|
||||||
|
_waba_ids_from_debug,
|
||||||
|
)
|
||||||
|
|
||||||
_NAME = re.compile(r"^[a-z0-9_]{1,512}$")
|
_NAME = re.compile(r"^[a-z0-9_]{1,512}$")
|
||||||
_LANG = re.compile(r"^[a-z]{2}(_[A-Z]{2})?$")
|
_LANG = re.compile(r"^[a-z]{2}(_[A-Z]{2})?$")
|
||||||
|
|
@ -510,6 +522,62 @@ async def _pull_templates(db: Session, account: WhatsAppBusinessAccount, token:
|
||||||
return seen
|
return seen
|
||||||
|
|
||||||
|
|
||||||
|
def _wrong_template_node(exc: HTTPException) -> bool:
|
||||||
|
detail = str(exc.detail).lower()
|
||||||
|
return "message_templates" in detail and "nonexisting field" in detail
|
||||||
|
|
||||||
|
|
||||||
|
async def _correct_waba_id(db: Session, account: WhatsAppBusinessAccount, token: str) -> bool:
|
||||||
|
"""Replace a stored phone-number ID with the WABA ID from the embedded-signup token."""
|
||||||
|
try:
|
||||||
|
debug = await _debug_token(db, token)
|
||||||
|
except HTTPException:
|
||||||
|
return False
|
||||||
|
wabas = _waba_ids_from_debug(debug)
|
||||||
|
phone_ids = set(_phone_ids_from_debug(debug))
|
||||||
|
stored = str(account.waba_id)
|
||||||
|
if not wabas or stored in wabas:
|
||||||
|
return False
|
||||||
|
real = wabas[0] if len(wabas) == 1 else None
|
||||||
|
if real is None:
|
||||||
|
for waba in wabas:
|
||||||
|
try:
|
||||||
|
rows = await _load_phones(token, waba)
|
||||||
|
except HTTPException:
|
||||||
|
continue
|
||||||
|
ids = {str(row.get("id")) for row in rows if row.get("id")}
|
||||||
|
if stored in ids or phone_ids.intersection(ids):
|
||||||
|
real = waba
|
||||||
|
break
|
||||||
|
if not real or real == stored:
|
||||||
|
return False
|
||||||
|
taken = db.scalar(
|
||||||
|
select(WhatsAppBusinessAccount.id).where(
|
||||||
|
WhatsAppBusinessAccount.waba_id == real,
|
||||||
|
WhatsAppBusinessAccount.id != account.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if taken is not None:
|
||||||
|
return False
|
||||||
|
account.waba_id = real
|
||||||
|
try:
|
||||||
|
info = await _load_waba(token, real)
|
||||||
|
name = str(info.get("name") or "").strip()
|
||||||
|
if name:
|
||||||
|
account.name = name[:200]
|
||||||
|
except HTTPException:
|
||||||
|
pass
|
||||||
|
user = db.get(UserAccount, account.user_id)
|
||||||
|
if user is not None:
|
||||||
|
try:
|
||||||
|
rows = await _load_phones(token, real)
|
||||||
|
_apply_phones(db, user, account, rows, None)
|
||||||
|
except HTTPException:
|
||||||
|
pass
|
||||||
|
db.flush()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount) -> str | None:
|
async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount) -> str | None:
|
||||||
tokens = _candidate_tokens(db, account)
|
tokens = _candidate_tokens(db, account)
|
||||||
if not tokens:
|
if not tokens:
|
||||||
|
|
@ -519,6 +587,12 @@ async def sync_account_templates(db: Session, account: WhatsAppBusinessAccount)
|
||||||
last_error: str | None = None
|
last_error: str | None = None
|
||||||
for index, token in enumerate(tokens):
|
for index, token in enumerate(tokens):
|
||||||
try:
|
try:
|
||||||
|
try:
|
||||||
|
seen = await _pull_templates(db, account, token)
|
||||||
|
except HTTPException as exc:
|
||||||
|
db.rollback()
|
||||||
|
if not _wrong_template_node(exc) or not await _correct_waba_id(db, account, token):
|
||||||
|
raise
|
||||||
seen = await _pull_templates(db, account, token)
|
seen = await _pull_templates(db, account, token)
|
||||||
except HTTPException as exc:
|
except HTTPException as exc:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
|
|
|
||||||
|
|
@ -112,11 +112,16 @@ function parseSignupMessage(raw: unknown): SignupSession | null {
|
||||||
const message = data as { type?: string; event?: string; data?: Record<string, unknown> }
|
const message = data as { type?: string; event?: string; data?: Record<string, unknown> }
|
||||||
if (message.type !== 'WA_EMBEDDED_SIGNUP' || !message.event) return null
|
if (message.type !== 'WA_EMBEDDED_SIGNUP' || !message.event) return null
|
||||||
const body = message.data || {}
|
const body = message.data || {}
|
||||||
|
const metaId = (value: unknown) => {
|
||||||
|
if (typeof value === 'string' && value.trim()) return value.trim()
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value)) return String(Math.trunc(value))
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
event: message.event,
|
event: message.event,
|
||||||
wabaId: typeof body.waba_id === 'string' ? body.waba_id : undefined,
|
wabaId: metaId(body.waba_id),
|
||||||
phoneNumberId: typeof body.phone_number_id === 'string' ? body.phone_number_id : undefined,
|
phoneNumberId: metaId(body.phone_number_id),
|
||||||
businessId: typeof body.business_id === 'string' ? body.business_id : undefined,
|
businessId: metaId(body.business_id),
|
||||||
errorMessage: typeof body.error_message === 'string' ? body.error_message : undefined,
|
errorMessage: typeof body.error_message === 'string' ? body.error_message : undefined,
|
||||||
currentStep: typeof body.current_step === 'string' ? body.current_step : undefined,
|
currentStep: typeof body.current_step === 'string' ? body.current_step : undefined,
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +316,7 @@ export function WhatsAppSetupPage() {
|
||||||
config_id: config.config_id,
|
config_id: config.config_id,
|
||||||
response_type: 'code',
|
response_type: 'code',
|
||||||
override_default_response_type: true,
|
override_default_response_type: true,
|
||||||
extras: {},
|
extras: { sessionInfoVersion: 3 },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} catch (reason) {
|
} catch (reason) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue