Skip to main content

Crate chat_system

Crate chat_system 

Source
Expand description

§chat-system

A multi-protocol async chat crate for Rust. Provides a single unified Messenger trait for IRC, Matrix, Discord, Telegram, Slack, Signal, WhatsApp, Microsoft Teams, Google Chat, iMessage, Webhook, and Console — with full rich-text support for every platform’s native format.

The primary way to use this crate is through the generic interface: MessengerConfig is a serde-tagged enum that selects the backend at runtime, so the protocol is just a field in your config file rather than a compile-time choice.


§Quick start — generic interface

use chat_system::{GenericMessenger, Messenger, MessengerConfig, PresenceStatus};
use chat_system::config::IrcConfig;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Build (or deserialize) a config — the protocol is just a field.
    let config = MessengerConfig::Irc(IrcConfig {
        name: "my-bot".into(),
        server: "irc.libera.chat".into(),
        port: 6697,
        nick: "my-bot".into(),
        channels: vec!["#rust".into()],
        tls: true,
    });

    // GenericMessenger implements Messenger — swap the config to change protocol.
    let mut client = GenericMessenger::new(config);
    client.initialize().await?;

    // Presence status (no-op on platforms that don't support it)
    client.set_status(PresenceStatus::Online).await?;

    // Text status / custom status message
    client.set_text_status("Building something with Rust 🦀").await?;

    client.send_message("#rust", "Hello from chat-system!").await?;

    // Receive messages
    for msg in client.receive_messages().await? {
        println!("[{}] {}: {}", msg.channel.as_deref().unwrap_or("?"), msg.sender, msg.content);
        // Each message may carry reactions (populated on platforms that support them)
        if let Some(reactions) = &msg.reactions {
            for r in reactions { println!("  {} × {}", r.emoji, r.count); }
        }
    }

    client.disconnect().await?;
    Ok(())
}

§Loading the config from a file

Because MessengerConfig derives serde::Deserialize, any serde-compatible source works.

TOML (config.toml):

protocol = "discord"
name     = "my-bot"
token    = "Bot TOKEN_HERE"
let toml_str = std::fs::read_to_string("config.toml")?;
let config: MessengerConfig = toml::from_str(&toml_str)?;
let mut client = GenericMessenger::new(config);
client.initialize().await?;

JSON (config.json):

{"protocol":"telegram","name":"my-bot","token":"BOT_TOKEN"}
let json_str = std::fs::read_to_string("config.json")?;
let config: MessengerConfig = serde_json::from_str(&json_str)?;
let mut client = GenericMessenger::new(config);
client.initialize().await?;

§Multi-platform with MessengerManager

MessengerManager holds a collection of Messenger instances and dispatches to all of them at once.

use chat_system::{GenericMessenger, Messenger, MessengerConfig, MessengerManager};
use chat_system::config::{DiscordConfig, TelegramConfig};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut mgr = MessengerManager::new()
        .add(GenericMessenger::new(MessengerConfig::Discord(DiscordConfig {
            name: "discord".into(),
            token: std::env::var("DISCORD_TOKEN")?,
        })))
        .add(GenericMessenger::new(MessengerConfig::Telegram(TelegramConfig {
            name: "telegram".into(),
            token: std::env::var("TELEGRAM_TOKEN")?,
        })));
    mgr.initialize_all().await?;

    // Broadcast to every connected platform
    mgr.broadcast("#general", "Hello from all platforms!").await;

    // Receive from every platform in one call
    for msg in mgr.receive_all().await? {
        println!("[{}] {}: {}", msg.channel.as_deref().unwrap_or("?"), msg.sender, msg.content);
    }

    mgr.disconnect_all().await?;
    Ok(())
}

§Reactions

// Add a reaction (no-op on platforms that don't support it)
client.add_reaction("msg-id-123", "#general", "👍").await?;
client.remove_reaction("msg-id-123", "#general", "👍").await?;

Incoming messages expose reactions via Message::reactions:

for msg in client.receive_messages().await? {
    if let Some(reactions) = &msg.reactions {
        for r in reactions {
            println!("{}: {} ({})", msg.id, r.emoji, r.count);
        }
    }
}

§Profile pictures

// Retrieve a user's profile picture URL (returns None if not supported)
if let Some(url) = client.get_profile_picture("user-id-123").await? {
    println!("Avatar: {url}");
}

// Update the bot's own profile picture
client.set_profile_picture("https://example.com/avatar.png").await?;

§Text status / custom status message

// Presence indicator (Online / Away / Busy / Invisible / Offline)
client.set_status(PresenceStatus::Busy).await?;

// Text status shown next to the username (Slack, Discord, …)
client.set_text_status("In a meeting 📅").await?;

§Replies

Use SendOptions to reply to a specific message:

client.send_message_with_options(SendOptions {
    recipient: "#general",
    content: "Thanks for the message!",
    reply_to: Some("original-message-id"),
    ..Default::default()
}).await?;

Incoming reply messages expose the parent via Message::reply_to.


§Search

let results = client.search_messages(SearchQuery {
    text: "deploy".into(),
    channel: Some("#ops".into()),
    limit: Some(20),
    ..Default::default()
}).await?;
for msg in results {
    println!("{}: {}", msg.sender, msg.content);
}

§Rich text

use chat_system::{RichText, RichTextNode};

let msg = RichText(vec![
    RichTextNode::Bold(vec![RichTextNode::Plain("Hello".into())]),
    RichTextNode::Plain(", world! ".into()),
    RichTextNode::Link {
        url: "https://example.com".into(),
        text: vec![RichTextNode::Plain("click".into())],
    },
]);

println!("{}", msg.to_discord_markdown());
println!("{}", msg.to_telegram_html());
println!("{}", msg.to_slack_mrkdwn());
println!("{}", msg.to_irc_formatted());

§Channel capabilities

Every ChannelType exposes its feature set via ChannelType::descriptor:

use chat_system::ChannelType;

let caps = ChannelType::Slack.descriptor().capabilities;
assert!(caps.supports_reactions);
assert!(caps.supports_threads);

for ct in ChannelType::ALL {
    println!("{:14} reactions={} threads={}", ct.display_name(),
        ct.descriptor().capabilities.supports_reactions,
        ct.descriptor().capabilities.supports_threads);
}

§Protocol-specific clients

When you need access to protocol-specific features not covered by the generic interface, you can construct the concrete messenger type directly:

use chat_system::messengers::IrcMessenger;
use chat_system::Messenger;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut client = IrcMessenger::new("my-bot", "irc.libera.chat", 6697, "my-bot")
        .with_tls(true)
        .with_channels(vec!["#rust"]);
    client.initialize().await?;
    client.send_message("#rust", "Hello, IRC!").await?;
    client.disconnect().await?;
    Ok(())
}

Re-exports§

pub use channel_type::ChannelCapabilities;
pub use channel_type::ChannelDescriptor;
pub use channel_type::ChannelType;
pub use channel_type::InboundMode;
pub use config::GenericMessenger;
pub use config::GenericServer;
pub use config::MessengerConfig;
pub use config::ServerConfig;
pub use markdown::chunk_markdown_html;
pub use markdown::markdown_to_slack;
pub use markdown::markdown_to_telegram_html;
pub use message::MediaAttachment;
pub use message::Message;
pub use message::Reaction;
pub use message::SendOptions;
pub use messenger::Messenger;
pub use messenger::MessengerManager;
pub use messenger::PresenceStatus;
pub use messenger::SearchQuery;
pub use rich_text::RichText;
pub use rich_text::RichTextNode;
pub use server::ChatListener;
pub use server::ChatServer;
pub use server::MessageHandler;
pub use server::Server;
pub use servers::IrcListener;

Modules§

channel_type
Channel/platform type definitions and capabilities.
config
Config-driven generic client and server types.
markdown
Markdown-to-platform conversion utilities.
message
Core message types.
messenger
The Messenger trait and MessengerManager.
messengers
Protocol-specific messenger implementations.
rich_text
Rich text representation and format conversion.
server
ChatServer and ChatListener traits for server-side implementations, plus the concrete protocol-agnostic Server.
servers
Concrete ChatListener implementations.