1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! Generic interface showcase — demonstrates [`GenericMessenger`] and the full
//! [`Messenger`] trait surface using the `console` backend (no network needed).
//!
//! This is the **recommended starting point** for new projects. Swap the config
//! to switch protocols without changing any application code.
//!
//! Run with:
//! cargo run --example generic_config_client
use chat_system::{
GenericMessenger, GenericServer, Messenger, MessengerManager, PresenceStatus, SearchQuery,
config::{ConsoleConfig, IrcConfig, IrcListenerConfig, MessengerConfig, ServerConfig},
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
println!("=== chat-system: generic interface demo ===\n");
// ── 1. Config serialization / deserialization ─────────────────────────────
// MessengerConfig is a serde-tagged enum. The `protocol` field picks the
// backend. This means you can store the whole config in TOML / JSON / YAML.
println!("--- Config round-trip ---");
let irc_config = MessengerConfig::Irc(IrcConfig {
name: "irc-bot".into(),
server: "irc.libera.chat".into(),
port: 6697,
nick: "generic-bot".into(),
channels: vec!["#rust".into()],
tls: true,
});
// Equivalent JSON:
// {"protocol":"irc","name":"irc-bot","server":"irc.libera.chat",
// "port":6697,"nick":"generic-bot","channels":["#rust"],"tls":true}
let json = serde_json::to_string_pretty(&irc_config)?;
println!("Serialized config:\n{json}");
let decoded: MessengerConfig = serde_json::from_str(&json)?;
println!(
"\nRound-trip OK → protocol={} name={}\n",
decoded.protocol_name(),
decoded.name()
);
// ── 2. Core send / receive with the console backend ───────────────────────
// The console backend writes to stdout and reads from stdin — perfect for
// demos and tests. Replace the config to use any other platform.
println!("--- Core send / receive (console backend) ---");
let console_cfg = MessengerConfig::Console(ConsoleConfig {
name: "console-bot".into(),
});
let mut bot = GenericMessenger::new(console_cfg);
bot.initialize().await?;
bot.send_message("world", "Hello from GenericMessenger!")
.await?;
bot.disconnect().await?;
// ── 3. Presence status ────────────────────────────────────────────────────
// set_status is a no-op on platforms that don't support it; real platforms
// (Slack, Discord, Matrix, …) will propagate the status to other users.
println!("\n--- Presence status ---");
let console_cfg2 = MessengerConfig::Console(ConsoleConfig {
name: "status-bot".into(),
});
let mut bot2 = GenericMessenger::new(console_cfg2);
bot2.initialize().await?;
for status in [
PresenceStatus::Online,
PresenceStatus::Away,
PresenceStatus::Busy,
PresenceStatus::Invisible,
PresenceStatus::Offline,
] {
bot2.set_status(status).await?;
println!(" set_status({status:?}) → ok");
}
// ── 4. Text status / custom status message ────────────────────────────────
// A short human-readable string displayed next to the username on platforms
// like Slack and Discord. Separate from the presence indicator above.
println!("\n--- Text status ---");
bot2.set_text_status("Building something in Rust 🦀")
.await?;
println!(" set_text_status → ok");
bot2.set_text_status("").await?;
println!(" cleared text status → ok");
// ── 5. Reactions ──────────────────────────────────────────────────────────
// add_reaction / remove_reaction are no-ops on platforms without reaction
// support (IRC, Console, …). On Slack, Discord, Matrix they send the emoji.
println!("\n--- Reactions ---");
bot2.add_reaction("msg-abc123", "#general", "👍").await?;
println!(" add_reaction(👍) → ok");
bot2.add_reaction("msg-abc123", "#general", "🎉").await?;
println!(" add_reaction(🎉) → ok");
bot2.remove_reaction("msg-abc123", "#general", "👍").await?;
println!(" remove_reaction(👍) → ok");
// ── 6. Profile pictures ───────────────────────────────────────────────────
println!("\n--- Profile pictures ---");
let pic = bot2.get_profile_picture("alice").await?;
println!(" get_profile_picture(\"alice\") → {pic:?}");
bot2.set_profile_picture("https://example.com/avatar.png")
.await?;
println!(" set_profile_picture → ok");
// ── 7. Message search ─────────────────────────────────────────────────────
// Returns empty on platforms without server-side search support.
// On Slack / Discord / Matrix this sends a real search query.
println!("\n--- Search ---");
let results = bot2
.search_messages(SearchQuery {
text: "hello".into(),
channel: Some("#general".into()),
limit: Some(10),
..Default::default()
})
.await?;
println!(" search(\"hello\") → {} result(s)", results.len());
bot2.disconnect().await?;
// ── 8. MessengerManager — multi-platform dispatch ─────────────────────────
println!("\n--- MessengerManager (multi-bot) ---");
let mut mgr = MessengerManager::new();
for (i, name) in ["alpha", "beta", "gamma"].iter().enumerate() {
let cfg = MessengerConfig::Console(ConsoleConfig {
name: (*name).into(),
});
let mut gm = GenericMessenger::new(cfg);
gm.initialize().await?;
println!(" added messenger #{i}: {name}");
mgr = mgr.add(gm);
}
mgr.broadcast("world", "broadcast message").await;
println!(
" broadcast → ok (sent to {} messengers)",
mgr.messengers().len()
);
mgr.disconnect_all().await?;
// ── 9. Server config round-trip ───────────────────────────────────────────
println!("\n--- Server config round-trip ---");
let server_cfg = ServerConfig {
name: "irc-server".into(),
listeners: vec![Box::new(IrcListenerConfig {
address: "127.0.0.1:16667".into(),
})],
};
let server_json = serde_json::to_string_pretty(&server_cfg)?;
println!("Server config:\n{server_json}");
println!("listener count: {}", server_cfg.listeners.len());
let _server = GenericServer::new(server_cfg);
// Uncomment to actually run the server:
// _server.run(|msg| async move {
// println!("received: {}", msg.content);
// Ok(Some(format!("echo: {}", msg.content)))
// }).await?;
println!("\nDone.");
Ok(())
}