# -----------------------------------------------------------
# Astra - WhatsApp Client Framework
# Licensed under the Apache License 2.0.
# -----------------------------------------------------------
"""
This module defines the identity and user representation in Astra.
It encapsulates WhatsApp JIDs and contact metadata.
"""
from dataclasses import dataclass
from typing import Optional, Dict, Any
[docs]
@dataclass(frozen=True)
class JID:
"""
Represents a WhatsApp Jabber ID (JID).
A JID uniquely identifies a user, group, or newsletter on WhatsApp.
Example: '1234567890@c.us' (User) or '1234567890@g.us' (Group).
"""
user: str
server: str
serialized: str
[docs]
@classmethod
def parse(cls, raw: str) -> "JID":
"""
Parses a raw JID string into a structured JID object.
Args:
raw: The raw WhatsApp ID string (e.g., "12345@c.us").
Returns:
A structured JID instance.
Example:
>>> jid = JID.parse("12345@c.us")
>>> print(jid.user)
"12345"
"""
if not raw:
return cls(user="unknown", server="unknown", serialized="unknown@unknown")
if "@" not in raw:
# Default to user server if missing
return cls(user=raw, server="c.us", serialized=f"{raw}@c.us")
username, server = raw.split("@", 1)
return cls(user=username, server=server, serialized=raw)
@property
def primary(self) -> str:
"""
Returns the primary (base) JID without device suffixes.
Example: '12345:4@lid' -> '12345@lid'
"""
if ":" in self.user:
base_user = self.user.split(":", 1)[0]
return f"{base_user}@{self.server}"
return self.serialized
def __str__(self) -> str:
return self.serialized
[docs]
@dataclass
class User:
"""
Represents a WhatsApp Actor - a person, group participant, or yourself.
This class holds metadata about a contact or user, such as their
display name and business status.
"""
id: JID
name: Optional[str] = None
push_name: Optional[str] = None
is_me: bool = False
is_business: bool = False
is_my_contact: bool = False
verified_name: Optional[str] = None
_client: Any = None # Bound Astra Client
@property
def title(self) -> str:
"""Standardized title property for entity resolution."""
return self.name or self.push_name or self.verified_name or self.id.user
[docs]
async def send_message(self, text: str, **kwargs) -> Any:
"""Sends a private message to this user."""
if not self._client: raise RuntimeError("User is not bound to a client.")
return await self._client.chat.send_message(self.id.serialized, text, **kwargs)
[docs]
async def block(self) -> bool:
"""Blocks this user."""
if not self._client: raise RuntimeError("User is not bound to a client.")
return await self._client.bridge.call("blockContact", self.id.serialized)
[docs]
@classmethod
def from_payload(cls, data: Dict[str, Any], client: Any = None) -> "User":
"""
Creates a User instance from a raw dictionary payload.
This is typically used when receiving data from the WhatsApp Web bridge.
Args:
data: The raw dictionary containing user data.
Returns:
A populated User instance.
"""
return cls(
id=JID.parse(data.get("id", "")),
name=data.get("name"),
push_name=data.get("pushname"),
is_me=data.get("isMe", False),
is_business=data.get("isBusiness", False),
is_my_contact=data.get("isMyContact", False),
verified_name=data.get("verifiedName"),
_client=client
)