Skip to main content

botkit_matrix/
client.rs

1use botkit_core::BotError;
2use matrix_sdk::ruma::OwnedEventId;
3use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
4use matrix_sdk::{Client, Room};
5
6/// Matrix client wrapper
7///
8/// Provides a simplified API for common Matrix operations.
9#[derive(Clone)]
10pub struct MatrixClient {
11    inner: Client,
12}
13
14impl MatrixClient {
15    /// Create a new Matrix client wrapper
16    pub fn new(client: Client) -> Self {
17        Self { inner: client }
18    }
19
20    /// Get the inner matrix-sdk Client for advanced operations
21    pub fn inner(&self) -> &Client {
22        &self.inner
23    }
24
25    /// Send a plain text message to a room
26    pub async fn send_message(&self, room: &Room, content: &str) -> Result<OwnedEventId, BotError> {
27        let msg = RoomMessageEventContent::text_plain(content);
28        let response = room
29            .send(msg)
30            .await
31            .map_err(|e| BotError::Api(e.to_string()))?;
32        Ok(response.response.event_id)
33    }
34
35    /// Send a formatted (HTML) message to a room
36    pub async fn send_formatted_message(
37        &self,
38        room: &Room,
39        plain: &str,
40        html: &str,
41    ) -> Result<OwnedEventId, BotError> {
42        let msg = RoomMessageEventContent::text_html(plain, html);
43        let response = room
44            .send(msg)
45            .await
46            .map_err(|e| BotError::Api(e.to_string()))?;
47        Ok(response.response.event_id)
48    }
49
50    /// Send a typing notification
51    pub async fn send_typing(&self, room: &Room, typing: bool) -> Result<(), BotError> {
52        room.typing_notice(typing)
53            .await
54            .map_err(|e| BotError::Api(e.to_string()))?;
55        Ok(())
56    }
57
58    /// Upload a file and send it to a room
59    ///
60    /// The MIME type is guessed from the filename; Matrix requires one, and
61    /// `application/octet-stream` is the safe fallback.
62    pub async fn send_file(
63        &self,
64        room: &Room,
65        filename: &str,
66        bytes: Vec<u8>,
67    ) -> Result<OwnedEventId, BotError> {
68        use matrix_sdk::attachment::AttachmentConfig;
69
70        let content_type = mime_guess::from_path(filename).first_or_octet_stream();
71
72        let response = room
73            .send_attachment(filename, &content_type, bytes, AttachmentConfig::new())
74            .await
75            .map_err(|e| BotError::Api(e.to_string()))?;
76
77        Ok(response.event_id)
78    }
79
80    /// React to a message with an emoji
81    pub async fn react(
82        &self,
83        room: &Room,
84        event_id: &OwnedEventId,
85        emoji: &str,
86    ) -> Result<OwnedEventId, BotError> {
87        use matrix_sdk::ruma::events::reaction::ReactionEventContent;
88        use matrix_sdk::ruma::events::relation::Annotation;
89
90        let reaction =
91            ReactionEventContent::new(Annotation::new(event_id.clone(), emoji.to_string()));
92
93        let response = room
94            .send(reaction)
95            .await
96            .map_err(|e| BotError::Api(e.to_string()))?;
97        Ok(response.response.event_id)
98    }
99}