Skip to content

whatsapp_baileys

whatsapp_baileys

WhatsAppBaileysChannel -- bidirectional WhatsApp messaging via Baileys protocol.

Spawns a Node.js subprocess that runs the Baileys bridge (JSON-line protocol on stdio). The bridge handles QR-code authentication, message sending, and incoming-message forwarding.

Classes

WhatsAppBaileysChannel

WhatsAppBaileysChannel(*, auth_dir: str = '', assistant_name: str = 'Jarvis', assistant_has_own_number: bool = False, bus: Optional[EventBus] = None)

Bases: BaseChannel

Bidirectional WhatsApp channel using the Baileys protocol.

Communicates with a Node.js bridge subprocess over JSON-line stdio.

PARAMETER DESCRIPTION
auth_dir

Directory for Baileys auth state persistence. Defaults to ~/.openjarvis/whatsapp_baileys_bridge/auth.

TYPE: str DEFAULT: ''

assistant_name

Display name used by the assistant in conversations.

TYPE: str DEFAULT: 'Jarvis'

assistant_has_own_number

If True the assistant has a dedicated WhatsApp number and will not filter out its own messages.

TYPE: bool DEFAULT: False

bus

Optional event bus for publishing channel events.

TYPE: Optional[EventBus] DEFAULT: None

Source code in src/openjarvis/channels/whatsapp_baileys.py
def __init__(
    self,
    *,
    auth_dir: str = "",
    assistant_name: str = "Jarvis",
    assistant_has_own_number: bool = False,
    bus: Optional[EventBus] = None,
) -> None:
    self._auth_dir = auth_dir
    self._assistant_name = assistant_name
    self._assistant_has_own_number = assistant_has_own_number
    self._bus = bus
    self._handlers: List[ChannelHandler] = []
    self._status = ChannelStatus.DISCONNECTED
    self._process: Optional[subprocess.Popen] = None
    self._reader_thread: Optional[threading.Thread] = None
    self._stop_event = threading.Event()
    self._runtime_dir = _DEFAULT_RUNTIME_DIR
    self._last_qr: str = ""
Functions
connect
connect() -> None

Spawn the Node.js bridge subprocess and start the reader thread.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def connect(self) -> None:
    """Spawn the Node.js bridge subprocess and start the reader thread."""
    if self._status == ChannelStatus.CONNECTED:
        return

    self._status = ChannelStatus.CONNECTING

    try:
        bridge_js = self._ensure_bridge()
    except RuntimeError as exc:
        logger.error("Bridge setup failed: %s", exc)
        self._status = ChannelStatus.ERROR
        return

    auth = self._auth_dir or str(self._runtime_dir / "auth")

    try:
        self._stop_event.clear()
        self._process = subprocess.Popen(
            ["node", str(bridge_js), "--auth-dir", auth],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            bufsize=1,
        )
        self._reader_thread = threading.Thread(
            target=self._reader_loop, daemon=True,
        )
        self._reader_thread.start()
        logger.info(
            "WhatsApp Baileys bridge started (pid=%s)",
            self._process.pid,
        )
    except Exception:
        logger.exception("Failed to start bridge subprocess")
        self._status = ChannelStatus.ERROR
disconnect
disconnect() -> None

Send disconnect command to the bridge and terminate the subprocess.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def disconnect(self) -> None:
    """Send disconnect command to the bridge and terminate the subprocess."""
    self._stop_event.set()

    if self._process is not None and self._process.stdin is not None:
        try:
            self._write_command({"type": "disconnect"})
        except Exception:
            logger.debug("Could not send disconnect command", exc_info=True)

    if self._process is not None:
        try:
            self._process.terminate()
            self._process.wait(timeout=5.0)
        except Exception:
            logger.debug("Bridge process termination error", exc_info=True)
        self._process = None

    if self._reader_thread is not None:
        self._reader_thread.join(timeout=5.0)
        self._reader_thread = None

    self._status = ChannelStatus.DISCONNECTED
send
send(channel: str, content: str, *, conversation_id: str = '', metadata: Dict[str, Any] | None = None) -> bool

Send a message to a WhatsApp JID via the bridge subprocess.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def send(
    self,
    channel: str,
    content: str,
    *,
    conversation_id: str = "",
    metadata: Dict[str, Any] | None = None,
) -> bool:
    """Send a message to a WhatsApp JID via the bridge subprocess."""
    if self._process is None or self._status != ChannelStatus.CONNECTED:
        logger.warning("Cannot send: bridge not connected")
        return False

    try:
        self._write_command({
            "type": "send",
            "jid": channel,
            "text": content,
        })
        self._publish_sent(channel, content, conversation_id)
        return True
    except Exception:
        logger.debug("WhatsApp Baileys send failed", exc_info=True)
        return False
status
status() -> ChannelStatus

Return the current connection status.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def status(self) -> ChannelStatus:
    """Return the current connection status."""
    return self._status
list_channels
list_channels() -> List[str]

Return available channel identifiers.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def list_channels(self) -> List[str]:
    """Return available channel identifiers."""
    return ["whatsapp_baileys"]
on_message
on_message(handler: ChannelHandler) -> None

Register a callback for incoming messages.

Source code in src/openjarvis/channels/whatsapp_baileys.py
def on_message(self, handler: ChannelHandler) -> None:
    """Register a callback for incoming messages."""
    self._handlers.append(handler)