# Channels The channels module lets OpenJarvis send and receive messages through external messaging platforms. Each platform has a dedicated channel implementation that connects directly to the platform's API -- there is no intermediate gateway. !!! note "Channels are disabled by default" The `[channel]` config section defaults to `enabled = false`. You must set `enabled = true` and configure platform-specific credentials before channel features become active. --- ## Overview Channel messaging is built around the `BaseChannel` ABC. Each platform (Telegram, Discord, Slack, WhatsApp, etc.) has its own implementation registered via `@ChannelRegistry.register("name")`. Channels connect directly to their platform APIs, register handlers for incoming messages, and send outgoing messages. ```mermaid graph LR A[Your Code] -->|send| B[TelegramChannel / DiscordChannel / SlackChannel / ...] B -->|Platform API| C[Telegram / Discord / Slack / ...] C -->|incoming messages| B B -->|on_message handlers| D[Your Handlers] ``` --- ## Supported Channels | Channel | Registry Key | Platform | Pip Extra | Auth | |---------|-------------|----------|-----------|------| | `TelegramChannel` | `telegram` | Telegram Bot API | `channel-telegram` | Bot token | | `DiscordChannel` | `discord` | Discord Bot API | `channel-discord` | Bot token | | `SlackChannel` | `slack` | Slack Web API | `channel-slack` | Bot + App tokens | | `WhatsAppChannel` | `whatsapp` | WhatsApp Business API | — | API token | | `WhatsAppBaileysChannel` | `whatsapp_baileys` | WhatsApp (Baileys) | — | QR code auth | | `WebhookChannel` | `webhook` | Generic HTTP webhook | — | URL + optional secret | | `EmailChannel` | `email` | SMTP/IMAP email | — | Email credentials | | `SignalChannel` | `signal` | Signal Messenger | — | Signal CLI | | `GoogleChatChannel` | `google_chat` | Google Chat | — | Service account | | `IRCChannel` | `irc` | IRC | — | Server credentials | | `WebChatChannel` | `webchat` | Browser-based chat | — | None | | `TeamsChannel` | `teams` | Microsoft Teams | — | Bot credentials | | `MatrixChannel` | `matrix` | Matrix protocol | — | Homeserver + token | | `MattermostChannel` | `mattermost` | Mattermost | — | Bot token | | `FeishuChannel` | `feishu` | Feishu/Lark | — | App credentials | | `BlueBubblesChannel` | `bluebubbles` | iMessage (BlueBubbles) | — | BlueBubbles server | | `LineChannel` | `line` | LINE Messaging API | `channel-line` | Channel access token | | `ViberChannel` | `viber` | Viber Bot API | `channel-viber` | Auth token | | `MessengerChannel` | `messenger` | Facebook Messenger | `channel-messenger` | Page access token | | `RedditChannel` | `reddit` | Reddit API | `channel-reddit` | OAuth credentials | | `MastodonChannel` | `mastodon` | Mastodon API | `channel-mastodon` | Access token | | `XMPPChannel` | `xmpp` | XMPP/Jabber | `channel-xmpp` | JID + password | | `RocketChatChannel` | `rocketchat` | Rocket.Chat API | `channel-rocketchat` | User credentials | | `ZulipChannel` | `zulip` | Zulip API | `channel-zulip` | Bot email + API key | | `TwitchChannel` | `twitch` | Twitch IRC/API | `channel-twitch` | OAuth token | | `NostrChannel` | `nostr` | Nostr protocol | `channel-nostr` | Private key (nsec) | --- ## Using a Channel ### Connecting ```python title="connect.py" from openjarvis.channels.telegram import TelegramChannel channel = TelegramChannel( bot_token="YOUR_BOT_TOKEN", # (1)! ) channel.connect() print(channel.status()) # ChannelStatus.CONNECTED ``` 1. Falls back to the `TELEGRAM_BOT_TOKEN` environment variable if not provided. ### Sending Messages ```python title="send_message.py" from openjarvis.channels.telegram import TelegramChannel channel = TelegramChannel() channel.connect() # Send to a chat by ID ok = channel.send( "123456789", "Analysis complete. Results are ready.", conversation_id="thread-abc123", # optional, for threading ) if ok: print("Message delivered") else: print("Delivery failed") channel.disconnect() ``` ### Receiving Messages Register handler callbacks before calling `connect()`. Each handler receives a `ChannelMessage` and can optionally return a reply string. ```python title="receive_messages.py" from openjarvis.channels._stubs import ChannelMessage from openjarvis.channels.discord_channel import DiscordChannel channel = DiscordChannel() def handle_incoming(msg: ChannelMessage) -> None: print(f"[{msg.channel}] {msg.sender}: {msg.content}") print(f" conversation_id={msg.conversation_id}") print(f" message_id={msg.message_id}") channel.on_message(handle_incoming) # (1)! channel.connect() # (2)! # Messages now arrive asynchronously via the background listener thread # Your main thread can continue doing other work ``` 1. Register one or more handlers. All registered handlers are called for every incoming message. 2. `connect()` starts the background listener thread after establishing the platform connection. ### Listing Available Channels ```python title="list_channels.py" from openjarvis.channels.slack import SlackChannel channel = SlackChannel() channel.connect() channels = channel.list_channels() print(channels) # ["general", "random", "dev"] ``` ### Disconnecting ```python title="disconnect.py" channel.disconnect() # Stops the listener thread and closes the platform connection # Status becomes ChannelStatus.DISCONNECTED ``` --- ## ChannelMessage Fields Every incoming message is delivered to handlers as a `ChannelMessage` dataclass. | Field | Type | Description | |-------|------|-------------| | `channel` | `str` | Name of the channel the message arrived on | | `sender` | `str` | Identifier of the message sender | | `content` | `str` | Message text | | `message_id` | `str` | Unique message identifier (may be empty) | | `conversation_id` | `str` | Thread/conversation identifier (may be empty) | | `session_id` | `str` | Session identifier (may be empty) | | `metadata` | `dict[str, Any]` | Additional platform-specific metadata | --- ## Event Bus Integration Pass an `EventBus` to publish channel events to the rest of the system: ```python title="channel_events.py" from openjarvis.core.events import EventBus, EventType from openjarvis.channels.telegram import TelegramChannel bus = EventBus() def on_received(event): print(f"Message received on {event.data['channel']}: {event.data['content']}") def on_sent(event): print(f"Message sent to {event.data['channel']}") bus.subscribe(EventType.CHANNEL_MESSAGE_RECEIVED, on_received) bus.subscribe(EventType.CHANNEL_MESSAGE_SENT, on_sent) channel = TelegramChannel(bus=bus) channel.connect() ``` | Event | Published When | Data Keys | |-------|----------------|-----------| | `CHANNEL_MESSAGE_RECEIVED` | A message arrives from the platform | `channel`, `sender`, `content`, `message_id` | | `CHANNEL_MESSAGE_SENT` | A message is successfully sent | `channel`, `content`, `conversation_id` | --- ## CLI Commands The `jarvis channel` subcommand group provides quick access to channel operations. ### List Channels ```bash jarvis channel list ``` ### Send a Message ```bash # Send to a channel by name jarvis channel send telegram "Build completed successfully" ``` ### Show Status ```bash jarvis channel status ``` --- ## API Server Endpoints When `jarvis serve` is running, three channel endpoints are available. Channels must be configured and enabled in `[channel]` for these endpoints to return data. ### `GET /v1/channels` Returns the list of registered channels and their status. ```bash curl http://localhost:8000/v1/channels ``` ```json { "channels": ["telegram", "discord", "slack"], "status": "connected" } ``` If no channels are configured: ```json {"channels": [], "message": "No channels configured"} ``` ### `POST /v1/channels/send` Send a message to a channel. ```bash curl -X POST http://localhost:8000/v1/channels/send \ -H "Content-Type: application/json" \ -d '{"channel": "telegram", "content": "Hello!", "conversation_id": "conv-1"}' ``` ```json {"status": "sent", "channel": "telegram"} ``` Required fields: `channel`, `content`. `conversation_id` is optional. ### `GET /v1/channels/status` Returns the connection status for each configured channel. ```bash curl http://localhost:8000/v1/channels/status ``` ```json {"status": "connected"} ``` Possible values: `connected`, `disconnected`, `connecting`, `error`, `not_configured`. --- ## Configuration Channel settings live in the `[channel]` section of `~/.openjarvis/config.toml`. Each platform has its own nested sub-section. ```toml title="~/.openjarvis/config.toml" [channel] enabled = true default_channel = "" default_agent = "simple" [channel.telegram] bot_token = "YOUR_TELEGRAM_BOT_TOKEN" [channel.discord] bot_token = "YOUR_DISCORD_BOT_TOKEN" [channel.slack] bot_token = "YOUR_SLACK_BOT_TOKEN" app_token = "YOUR_SLACK_APP_TOKEN" ``` ### Configuration Reference | Key | Type | Default | Description | |-----|------|---------|-------------| | `enabled` | `bool` | `false` | Enable channel messaging | | `default_channel` | `str` | `""` | Default channel to use when not specified | | `default_agent` | `str` | `simple` | Agent to use for handling inbound messages | Platform-specific settings are configured in nested sub-sections (e.g., `[channel.telegram]`, `[channel.discord]`). --- ## Complete Example This example connects a Telegram channel, registers a handler that echoes messages back, sends a test message, and then disconnects after a short wait. ```python title="full_example.py" import time from openjarvis.channels._stubs import ChannelMessage from openjarvis.channels.telegram import TelegramChannel from openjarvis.core.events import EventBus bus = EventBus() channel = TelegramChannel( bot_token="YOUR_BOT_TOKEN", bus=bus, ) received_messages = [] def on_message(msg: ChannelMessage) -> None: received_messages.append(msg) print(f"Received from {msg.sender} on #{msg.channel}: {msg.content}") channel.on_message(on_message) channel.connect() # List available channels channels = channel.list_channels() print(f"Available channels: {channels}") # Send a message if channels: channel.send(channels[0], "Hello from OpenJarvis!") # Wait for incoming messages time.sleep(10) channel.disconnect() print(f"Total messages received: {len(received_messages)}") ``` --- ## WhatsAppBaileysChannel `WhatsAppBaileysChannel` is registered as `"whatsapp_baileys"` in `ChannelRegistry` and provides **bidirectional WhatsApp messaging** using the Baileys protocol. It spawns a Node.js bridge subprocess that handles QR-code authentication, incoming message forwarding, and outbound message delivery. !!! warning "Node.js 22+ required" The Baileys bridge is a compiled Node.js application bundled inside the package. It is auto-installed to `~/.openjarvis/whatsapp_baileys_bridge/` on first `connect()` call. If `node` is not found on `PATH`, `connect()` logs an error and sets the channel to `ChannelStatus.ERROR`. !!! note "WhatsApp account required" WhatsApp does not offer an official API for personal accounts. Baileys operates on the WhatsApp Web protocol. You must scan a QR code with your WhatsApp mobile app to authenticate on first use. ### Connecting ```python title="whatsapp_connect.py" from openjarvis.channels.whatsapp_baileys import WhatsAppBaileysChannel channel = WhatsAppBaileysChannel( assistant_name="Jarvis", # (1)! assistant_has_own_number=False, # (2)! ) channel.connect() # spawns the Node.js bridge subprocess ``` 1. Display name used in conversation context. 2. Set `True` if the assistant has a dedicated WhatsApp number and should not filter its own messages. On first connection, the bridge will print a QR code to the terminal. Scan it with the WhatsApp app on your phone to authenticate. Authentication state is saved to `~/.openjarvis/whatsapp_baileys_bridge/auth/` and reused on subsequent connections. ### Receiving Messages ```python title="whatsapp_receive.py" from openjarvis.channels._stubs import ChannelMessage from openjarvis.channels.whatsapp_baileys import WhatsAppBaileysChannel channel = WhatsAppBaileysChannel() def on_message(msg: ChannelMessage) -> None: print(f"[{msg.sender}] {msg.content}") # msg.conversation_id is the WhatsApp JID (e.g. "15551234567@s.whatsapp.net") channel.on_message(on_message) channel.connect() # Background reader thread is running; your code continues here ``` ### Sending Messages Messages are addressed by WhatsApp **JID** (Jabber ID) -- the canonical identifier for a WhatsApp contact or group. ```python title="whatsapp_send.py" # Individual contact JID format: @s.whatsapp.net # Group JID format: @g.us ok = channel.send( "15551234567@s.whatsapp.net", # JID of the recipient "Hello from OpenJarvis!", ) if not ok: print("Send failed -- check that the bridge is connected") ``` ### Disconnecting ```python title="whatsapp_disconnect.py" channel.disconnect() # Sends disconnect command to bridge, terminates subprocess, stops reader thread ``` ### Constructor Parameters | Parameter | Type | Default | Description | |----------------------------|------------|-------------|--------------------------------------------------------| | `auth_dir` | `str` | `~/.openjarvis/whatsapp_baileys_bridge/auth` | Baileys auth state directory | | `assistant_name` | `str` | `"Jarvis"` | Display name for the assistant | | `assistant_has_own_number` | `bool` | `False` | Whether the assistant has a dedicated WhatsApp number | | `bus` | `EventBus` | `None` | Event bus for publishing channel events | ### Bridge Events The Node.js bridge communicates with Python via JSON lines on stdio. Python interprets the following event types: | Bridge event type | Effect | |-------------------|-------------------------------------------------------------| | `status` | Updates `ChannelStatus` (`connected` / `disconnected`) | | `qr` | Logs "QR code received -- scan to authenticate" | | `message` | Dispatches to all registered `on_message` handlers | | `error` | Logs the error and sets status to `ChannelStatus.ERROR` | ### Event Bus Integration When a `bus` is provided, `WhatsAppBaileysChannel` publishes the same events as other channels: | Event | Published When | Data Keys | |-------|----------------|-----------| | `CHANNEL_MESSAGE_RECEIVED` | An inbound WhatsApp message arrives | `channel`, `sender`, `content`, `message_id` | | `CHANNEL_MESSAGE_SENT` | A message is successfully sent | `channel`, `content`, `conversation_id` | ### Configuration WhatsApp Baileys channel settings live in the `[channel.whatsapp_baileys]` subsection: ```toml title="~/.openjarvis/config.toml" [channel.whatsapp_baileys] auth_dir = "/home/user/.openjarvis/whatsapp_baileys_bridge/auth" assistant_name = "Jarvis" assistant_has_own_number = false ``` --- ## See Also - [Architecture: Channels](../architecture/channels.md) -- listener loop internals and channel design - [API Reference: Channels](../api-reference/openjarvis/channels/index.md) -- full class and type signatures - [Getting Started: Configuration](../getting-started/configuration.md) -- full config reference - [User Guide: Agents](agents.md) -- agent system documentation