//! Example: Implementing a custom Channel for ZeroClaw //! //! Channels let ZeroClaw communicate through any messaging platform. //! Implement the Channel trait, register it, and the agent works everywhere. use anyhow::Result; use async_trait::async_trait; use tokio::sync::mpsc; /// Mirrors src/channels/traits.rs #[derive(Debug, Clone)] pub struct ChannelMessage { pub id: String, pub sender: String, /// Channel-specific reply address (e.g. Telegram chat_id, Discord channel_id). pub reply_target: String, pub content: String, pub channel: String, pub timestamp: u64, } #[async_trait] pub trait Channel: Send + Sync { fn name(&self) -> &str; async fn send(&self, message: &str, recipient: &str) -> Result<()>; async fn listen(&self, tx: mpsc::Sender) -> Result<()>; async fn health_check(&self) -> bool; } /// Example: Telegram channel via Bot API pub struct TelegramChannel { bot_token: String, allowed_users: Vec, client: reqwest::Client, } impl TelegramChannel { pub fn new(bot_token: &str, allowed_users: Vec) -> Self { Self { bot_token: bot_token.to_string(), allowed_users, client: reqwest::Client::new(), } } fn api_url(&self, method: &str) -> String { format!("https://api.telegram.org/bot{}/{method}", self.bot_token) } } #[async_trait] impl Channel for TelegramChannel { fn name(&self) -> &str { "telegram" } async fn send(&self, message: &str, chat_id: &str) -> Result<()> { self.client .post(self.api_url("sendMessage")) .json(&serde_json::json!({ "chat_id": chat_id, "text": message, "parse_mode": "Markdown", })) .send() .await?; Ok(()) } async fn listen(&self, tx: mpsc::Sender) -> Result<()> { let mut offset: i64 = 0; loop { let resp = self .client .get(self.api_url("getUpdates")) .query(&[("offset", offset.to_string()), ("timeout", "30".into())]) .send() .await? .json::() .await?; if let Some(updates) = resp["result"].as_array() { for update in updates { if let Some(msg) = update.get("message") { let sender = msg["from"]["username"] .as_str() .unwrap_or("unknown") .to_string(); if !self.allowed_users.is_empty() && !self.allowed_users.contains(&sender) { continue; } let chat_id = msg["chat"]["id"].to_string(); let channel_msg = ChannelMessage { id: msg["message_id"].to_string(), sender, reply_target: chat_id, content: msg["text"].as_str().unwrap_or("").to_string(), channel: "telegram".into(), timestamp: msg["date"].as_u64().unwrap_or(0), }; if tx.send(channel_msg).await.is_err() { return Ok(()); } } offset = update["update_id"].as_i64().unwrap_or(offset) + 1; } } } } async fn health_check(&self) -> bool { self.client .get(self.api_url("getMe")) .send() .await .map(|r| r.status().is_success()) .unwrap_or(false) } } fn main() { println!("This is an example — see CONTRIBUTING.md for integration steps."); println!("Add your channel config to ChannelsConfig in src/config/schema.rs"); }