Skip to main content

chat_system/
messenger.rs

1//! The [`Messenger`] trait and [`MessengerManager`].
2
3use crate::message::{Message, SendOptions};
4use anyhow::Result;
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7
8/// The presence/availability status of a messenger account or bot.
9///
10/// Not every platform supports every variant; unsupported values fall back to
11/// the closest equivalent or are silently ignored via the default no-op
12/// implementation of [`Messenger::set_status`].
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum PresenceStatus {
16    /// Fully available and accepting messages.
17    Online,
18    /// Temporarily away (e.g. idle, away message set).
19    Away,
20    /// Do-not-disturb / busy — notifications may be suppressed.
21    Busy,
22    /// Signed in but appearing as offline to other users.
23    Invisible,
24    /// Fully offline / disconnected.
25    Offline,
26}
27
28/// Structured query for searching messages.
29///
30/// All fields are optional; only the fields provided are used as filters.
31/// Platforms that do not support a particular filter silently ignore it.
32///
33/// The struct is serde-serializable so it can be loaded from config files or
34/// forwarded over APIs.
35#[derive(Debug, Clone, Default, Serialize, Deserialize)]
36pub struct SearchQuery {
37    /// Free-text search string (empty string matches all messages).
38    #[serde(default)]
39    pub text: String,
40    /// Restrict the search to a particular channel or conversation ID.
41    #[serde(default)]
42    pub channel: Option<String>,
43    /// Restrict to messages from a specific sender ID / username.
44    #[serde(default)]
45    pub from: Option<String>,
46    /// Maximum number of results to return.
47    #[serde(default)]
48    pub limit: Option<usize>,
49    /// Return only messages sent before this Unix timestamp (exclusive).
50    #[serde(default)]
51    pub before_timestamp: Option<i64>,
52    /// Return only messages sent after this Unix timestamp (exclusive).
53    #[serde(default)]
54    pub after_timestamp: Option<i64>,
55}
56
57/// A unified interface for chat platform clients.
58#[async_trait]
59pub trait Messenger: Send + Sync {
60    fn name(&self) -> &str;
61    fn messenger_type(&self) -> &str;
62    async fn initialize(&mut self) -> Result<()>;
63    async fn send_message(&self, recipient: &str, content: &str) -> Result<String>;
64    async fn send_message_with_options(&self, opts: SendOptions<'_>) -> Result<String> {
65        self.send_message(opts.recipient, opts.content).await
66    }
67    async fn receive_messages(&self) -> Result<Vec<Message>>;
68    fn is_connected(&self) -> bool;
69    async fn disconnect(&mut self) -> Result<()>;
70    async fn set_typing(&self, _channel: &str, _typing: bool) -> Result<()> {
71        Ok(())
72    }
73    /// Set the bot's own presence/availability status.
74    ///
75    /// Platforms that do not support a particular [`PresenceStatus`] value, or
76    /// that have no presence API at all, may ignore this call.  The default
77    /// implementation is a no-op so that existing messenger implementations
78    /// are unaffected.
79    async fn set_status(&self, _status: PresenceStatus) -> Result<()> {
80        Ok(())
81    }
82
83    /// Add an emoji reaction to a message.
84    ///
85    /// `message_id` is the platform message ID, `channel` is the channel or
86    /// conversation it belongs to, and `emoji` is the reaction emoji (Unicode
87    /// character or platform shortcode).
88    ///
89    /// Platforms that do not support reactions return `Ok(())` silently via
90    /// this default implementation.
91    async fn add_reaction(&self, _message_id: &str, _channel: &str, _emoji: &str) -> Result<()> {
92        Ok(())
93    }
94
95    /// Remove an emoji reaction from a message.
96    ///
97    /// Has the same signature as [`add_reaction`](Messenger::add_reaction).
98    /// Platforms that do not support reactions return `Ok(())` silently.
99    async fn remove_reaction(&self, _message_id: &str, _channel: &str, _emoji: &str) -> Result<()> {
100        Ok(())
101    }
102
103    /// Retrieve the profile-picture URL for a user.
104    ///
105    /// Returns `Ok(None)` on platforms that do not expose profile pictures or
106    /// when the user has no picture set.
107    async fn get_profile_picture(&self, _user_id: &str) -> Result<Option<String>> {
108        Ok(None)
109    }
110
111    /// Update the bot's own profile picture.
112    ///
113    /// `url` may be an HTTP URL or a `file://` path depending on what the
114    /// platform accepts.  Platforms that do not support this operation silently
115    /// return `Ok(())`.
116    async fn set_profile_picture(&self, _url: &str) -> Result<()> {
117        Ok(())
118    }
119
120    /// Set the bot's text status / custom status message.
121    ///
122    /// This is distinct from [`set_status`](Messenger::set_status), which
123    /// controls the presence indicator (online/away/busy/…).  A text status is
124    /// a short human-readable string displayed next to the user's name on
125    /// platforms that support it (e.g. Slack, Discord).
126    ///
127    /// Platforms that do not support text statuses silently return `Ok(())`.
128    async fn set_text_status(&self, _text: &str) -> Result<()> {
129        Ok(())
130    }
131
132    /// Search for messages matching `query`.
133    ///
134    /// Returns an empty `Vec` on platforms that do not support server-side
135    /// search.  Results are returned in an unspecified order unless the
136    /// platform guarantees one.
137    async fn search_messages(&self, _query: SearchQuery) -> Result<Vec<Message>> {
138        Ok(Vec::new())
139    }
140
141    /// Edit a previously sent message.
142    ///
143    /// `message_id` is the platform message ID, `channel` is the channel or
144    /// conversation it belongs to, and `new_content` is the replacement text.
145    ///
146    /// Platforms that do not support editing return `Ok(())` silently via
147    /// this default implementation.
148    async fn edit_message(
149        &self,
150        _message_id: &str,
151        _channel: &str,
152        _new_content: &str,
153    ) -> Result<()> {
154        Ok(())
155    }
156
157    /// Delete a previously sent message.
158    ///
159    /// Platforms that do not support deletion return `Ok(())` silently.
160    async fn delete_message(&self, _message_id: &str, _channel: &str) -> Result<()> {
161        Ok(())
162    }
163
164    /// Pin a message in a channel.
165    ///
166    /// Platforms that do not support pinning return `Ok(())` silently.
167    async fn pin_message(&self, _message_id: &str, _channel: &str) -> Result<()> {
168        Ok(())
169    }
170
171    /// Unpin a message in a channel.
172    ///
173    /// Platforms that do not support pinning return `Ok(())` silently.
174    async fn unpin_message(&self, _message_id: &str, _channel: &str) -> Result<()> {
175        Ok(())
176    }
177
178    /// Get the list of members in a channel or conversation.
179    ///
180    /// Returns an empty `Vec` on platforms that do not expose membership lists.
181    async fn get_channel_members(&self, _channel: &str) -> Result<Vec<String>> {
182        Ok(Vec::new())
183    }
184}
185
186/// Manages multiple [`Messenger`] instances.
187pub struct MessengerManager {
188    messengers: Vec<Box<dyn Messenger>>,
189}
190
191impl MessengerManager {
192    pub fn new() -> Self {
193        Self {
194            messengers: Vec::new(),
195        }
196    }
197
198    #[allow(clippy::should_implement_trait)]
199    pub fn add(mut self, messenger: impl Messenger + 'static) -> Self {
200        self.messengers.push(Box::new(messenger));
201        self
202    }
203
204    pub fn add_boxed(mut self, messenger: Box<dyn Messenger>) -> Self {
205        self.messengers.push(messenger);
206        self
207    }
208
209    pub async fn initialize_all(&mut self) -> Result<()> {
210        for m in &mut self.messengers {
211            m.initialize().await?;
212        }
213        Ok(())
214    }
215
216    pub async fn disconnect_all(&mut self) -> Result<()> {
217        for m in &mut self.messengers {
218            m.disconnect().await?;
219        }
220        Ok(())
221    }
222
223    pub async fn receive_all(&self) -> Result<Vec<Message>> {
224        let mut all = Vec::new();
225        for m in &self.messengers {
226            match m.receive_messages().await {
227                Ok(mut msgs) => all.append(&mut msgs),
228                Err(e) => tracing::warn!(messenger = %m.name(), "receive error: {e}"),
229            }
230        }
231        Ok(all)
232    }
233
234    pub async fn broadcast(
235        &self,
236        recipient: impl AsRef<str>,
237        content: impl AsRef<str>,
238    ) -> Vec<Result<String>> {
239        let mut results = Vec::new();
240        for m in &self.messengers {
241            results.push(m.send_message(recipient.as_ref(), content.as_ref()).await);
242        }
243        results
244    }
245
246    pub fn messengers(&self) -> &[Box<dyn Messenger>] {
247        &self.messengers
248    }
249
250    pub fn get(&self, name: impl AsRef<str>) -> Option<&dyn Messenger> {
251        self.messengers
252            .iter()
253            .find(|m| m.name() == name.as_ref())
254            .map(|b| b.as_ref())
255    }
256}
257
258impl Default for MessengerManager {
259    fn default() -> Self {
260        Self::new()
261    }
262}