glitch_in_the_matrix/
room.rs

1//! Abstractions for Matrix rooms.
2
3use types::replies::*;
4use types::messages::Message;
5use types::events::Event;
6use types::content::room::PowerLevels;
7use crate::request::{MatrixRequestable, MatrixRequest};
8use serde::Serialize;
9use serde::de::DeserializeOwned;
10use futures::*;
11use crate::errors::*;
12use http::Method;
13
14pub use types::room::Room;
15
16/// Trait used to implement methods on `Room`.
17///
18/// This exists because `Room` is defined in another crate (`gm-types`), which
19/// has no knowledge of `MatrixClient`. It contains most of the interesting
20/// methods for `Room` - most notably, you can make a `RoomClient`, which is how
21/// you actually do anything room-related.
22pub trait RoomExt<'a> {
23    /// Use an implementor of `MatrixRequestable` to make a `RoomClient`, an object used to call
24    /// endpoints relating to rooms.
25    ///
26    /// If you want to do pretty much anything *with* this `Room`, you probably
27    /// want to call this at some point.
28    fn cli<'b, 'c, T>(&'b self, cli: &'c mut T) -> RoomClient<'b, 'a, 'c, T> where T: MatrixRequestable;
29}
30/// Contains endpoints relating to creating or joining rooms.
31pub struct NewRoom;
32impl NewRoom {
33    /// Requests that the server resolve a room alias to a room (ID).
34    ///
35    /// The server will use the federation API to resolve the alias if the
36    /// domain part of the alias does not correspond to the server's own domain.
37    pub fn from_alias<R: MatrixRequestable>(cli: &mut R, alias: &str) -> impl Future<Item = Room<'static>, Error = MatrixError> {
38        MatrixRequest::new_basic(Method::GET, format!("/directory/room/{}", alias))
39            .send(cli)
40            .map(|RoomAliasReply { room, .. }| room)
41    }
42    /// Joins a room by identifier or alias.
43    pub fn join<R: MatrixRequestable>(cli: &mut R, room: &str) -> impl Future<Item = Room<'static>, Error = MatrixError> {
44        MatrixRequest::new_basic(Method::POST, format!("/join/{}", room))
45            .send(cli)
46            .map(|JoinReply { room }| room)
47    }
48    /// Creates a room, with given options.
49    pub fn create<R: MatrixRequestable>(cli: &mut R, opts: RoomCreationOptions) -> impl Future<Item = Room<'static>, Error = MatrixError> {
50        MatrixRequest::new_with_body_ser(Method::POST, "/createRoom", opts)
51            .send(cli)
52            .map(|JoinReply { room }| room)
53    }
54}
55/// A `Room` with a `MatrixRequestable` type, which you can use to call endpoints relating
56/// to rooms.
57pub struct RoomClient<'a, 'b: 'a, 'c, T: 'c> {
58    /// A reference to a room.
59    pub room: &'a Room<'b>,
60    /// A reference to a `MatrixRequestable` type.
61    pub cli: &'c mut T
62}
63impl<'a> RoomExt<'a> for Room<'a> {
64   fn cli<'b, 'c, T>(&'b self, cli: &'c mut T) -> RoomClient<'b, 'a, 'c, T> where T: MatrixRequestable {
65        RoomClient {
66            room: self,
67            cli
68        }
69    }
70}
71impl<'a, 'b, 'c, R> RoomClient<'a, 'b, 'c, R> where R: MatrixRequestable {
72    /// Sends a message to this room.
73    pub fn send(&mut self, msg: Message) -> impl Future<Item = SendReply, Error = MatrixError> {
74        MatrixRequest::new_with_body_ser(
75            Method::PUT,
76            format!("/rooms/{}/send/m.room.message/{}",
77                    self.room.id,
78                    self.cli.get_txnid()),
79            msg
80        ).send(self.cli)
81    }
82    /// Wrapper function that sends a `Message::Notice` with the specified unformatted text
83    /// to this room. Provided for convenience purposes.
84    pub fn send_simple<T: Into<String>>(&mut self, msg: T) -> impl Future<Item = SendReply, Error = MatrixError> {
85        let msg = Message::Notice {
86            body: msg.into(),
87            formatted_body: None,
88            format: None
89        };
90        self.send(msg)
91    }
92    /// Wrapper function that sends a `Message::Notice` with the specified HTML-formatted text
93    /// (and accompanying unformatted text, if given) to this room.
94    pub fn send_html<T, U>(&mut self, msg: T, unformatted: U) -> impl Future<Item = SendReply, Error = MatrixError>
95        where T: Into<String>, U: Into<Option<String>> {
96        let m = msg.into();
97        let msg = Message::Notice {
98            body: unformatted.into().unwrap_or(m.clone()),
99            formatted_body: Some(m),
100            format: Some("org.matrix.custom.html".into())
101        };
102        self.send(msg)
103    }
104    /// Send a read receipt for a given event ID.
105    pub fn read_receipt(&mut self, eventid: &str) -> impl Future<Item = (), Error = MatrixError> {
106        MatrixRequest::new_basic(Method::POST, format!("/rooms/{}/receipt/m.read/{}", self.room.id, eventid))
107            .discarding_send(self.cli)
108    }
109    /// Looks up the contents of a state event with type `ev_type` and state key
110    /// `key` in a room. If the user is joined to the room then the state is
111    /// taken from the current state of the room. If the user has left the room
112    /// then the state is taken from the state of the room when they left.
113    ///
114    /// The return value here can be any object that implements `Deserialize`,
115    /// allowing you to use the state API to store arbitrary objects. Common
116    /// state events, such as `m.room.name`, can be found in the `content`
117    /// module (`content::room::Name` for `m.room.name`).
118    ///
119    /// If the event was not found, an error will be thrown of type
120    /// `HttpCode(http::StatusCode::NotFound)`.
121    pub fn get_typed_state<T: DeserializeOwned + 'static>(&mut self, ev_type: &str, key: Option<&str>) -> impl Future<Item = T, Error = MatrixError> {
122        MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/state/{}/{}",
123                                              self.room.id,
124                                              ev_type,
125                                              key.unwrap_or("")))
126            .send(self.cli)
127    }
128    pub fn get_typed_state_opt<T: DeserializeOwned + 'static>(&mut self, ev_type: &str, key: Option<&str>) -> impl Future<Item = Option<T>, Error = MatrixError> {
129       self.get_typed_state(ev_type, key)
130          .then(move |res| {
131             match res {
132                Ok(res) => Ok(Some(res)),
133                Err(e) => {
134                   if let MatrixError::BadRequest(ref brk) = e {
135                      if brk.errcode == "M_NOT_FOUND" {
136                         return Ok(None)
137                      }
138                   }
139                   Err(e)
140                }
141             }
142          })
143    }
144    /// State events can be sent using this endpoint. These events will be
145    /// overwritten if the <event type> (`ev_type`) and <state key> (`key`) all
146    /// match.
147    ///
148    /// Like `get_state`, the value here can be any object that implements
149    /// `Serialize`, allowing you to use the state API to store arbitrary
150    /// objects. See the `get_state` docs for more.
151    pub fn set_typed_state<T: Serialize>(&mut self, ev_type: &str, key: Option<&str>, val: T) -> impl Future<Item = SetStateReply, Error = MatrixError> {
152        MatrixRequest::new_with_body_ser(
153            Method::PUT,
154            format!("/rooms/{}/state/{}/{}",
155                    self.room.id,
156                    ev_type, key.unwrap_or("")),
157            val
158        ).send(self.cli)
159    }
160    /// Get the state events for the current state of a room.
161    pub fn get_all_state(&mut self) -> impl Future<Item = Vec<Event>, Error = MatrixError> {
162        MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/state", self.room.id))
163            .send(self.cli)
164    }
165    /// Get a single event for this room, based on `event_id`.
166    ///
167    /// You must have permission to retrieve this event (e.g. by being a member of the room for
168    /// this event)
169    pub fn get_event(&mut self, id: &str) -> impl Future<Item = Event, Error = MatrixError> {
170        MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/event/{}", self.room.id, id))
171            .send(self.cli)
172    }
173    /// Get the list of member events for this room.
174    pub fn get_members(&mut self) -> impl Future<Item = ChunkReply, Error = MatrixError> {
175        MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/members", self.room.id))
176            .send(self.cli)
177    }
178    /// Get the list of joined members in this room.
179    ///
180    /// The current user must be in the room for it to work, unless it is an
181    /// Application Service, in which case any of the AS's users must be in the room.
182    ///
183    /// This API is primarily for Application Services and should be faster to
184    /// respond than `get_members()`, as it can be implemented more efficiently
185    /// on the server.
186    pub fn get_joined_members(&mut self) -> impl Future<Item = JoinedMembersReply, Error = MatrixError> {
187        MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/joined_members", self.room.id))
188            .send(self.cli)
189    }
190    /// This API returns a list of message and state events for a room. It uses
191    /// pagination query parameters to paginate history in the room.
192    ///
193    /// ## Parameters
194    ///
195    /// - `from`: The token to start returning events from. This token can be
196    ///   obtained from a `prev_batch` token returned for each room by the sync
197    ///   API, or from a `start` or `end` token returned by a previous request to
198    ///   this endpoint.
199    /// - `to`: The token to stop returning events at. This token can be
200    ///   obtained from a prev_batch token returned for each room by the sync
201    ///   endpoint, or from a start or end token returned by a previous request to
202    ///   this endpoint.
203    /// - `backward`: Whether to paginate backward, or forward; true = back-pagination.
204    /// - `limit`: The maximum number of events to return. (default: 10)
205    pub fn get_messages(&mut self, from: &str, to: Option<&str>, backward: bool, limit: Option<u32>) -> impl Future<Item = MessagesReply, Error = MatrixError> {
206        let mut req = MatrixRequest::new_basic(Method::GET, format!("/rooms/{}/messages", self.room.id));
207        req.params.insert("from".into(), from.into());
208        if let Some(to) = to {
209            req.params.insert("to".into(), to.into());
210        }
211        let dir = if backward { "b" } else { "f" };
212        req.params.insert("dir".into(), dir.into());
213        if let Some(limit) = limit {
214            req.params.insert("limit".into(), limit.to_string().into());
215        }
216        req.send(self.cli)
217    }
218    /// Strips all information out of an event which isn't critical to the
219    /// integrity of the server-side representation of the room.
220    ///
221    /// This cannot be undone.
222    ///
223    /// Users may redact their own events, and any user with a power level
224    /// greater than or equal to the redact power level of the room may redact
225    /// events there.
226    pub fn redact(&mut self, eventid: &str, reason: Option<&str>) -> impl Future<Item = (), Error = MatrixError> {
227        let mut body = vec![];
228        body.extend(reason.map(|x| ("reason", x.to_string())));
229        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/redact/{}/{}",
230                                                   self.room.id, eventid, self.cli.get_txnid()),
231                                     body)
232            .discarding_send(self.cli)
233    }
234    /// This tells the server that the user is typing for the next N
235    /// milliseconds where N is the value specified in the timeout key.
236    /// Alternatively, if typing is false, it tells the server that the user has
237    /// stopped typing.
238    pub fn typing(&mut self, typing: bool, timeout: Option<usize>) -> impl Future<Item = (), Error = MatrixError> {
239        let mut body = vec![("typing", typing.to_string())];
240        body.extend(timeout.map(|x| ("timeout", x.to_string())));
241        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/typing/{}",
242                                                   self.room.id, self.cli.get_user_id()),
243                                     body)
244            .discarding_send(self.cli)
245
246    }
247    /// This API starts a user participating in a particular room, if that user
248    /// is allowed to participate in that room. After this call, the client is
249    /// allowed to see all current state events in the room, and all subsequent
250    /// events associated with the room until the user leaves the room.
251    ///
252    /// After a user has joined a room, the room will appear as an entry in the
253    /// values in the `SyncStream`.
254    pub fn join(&mut self) -> impl Future<Item = (), Error = MatrixError> {
255        MatrixRequest::new_basic(Method::POST, format!("/rooms/{}/join", self.room.id))
256            .discarding_send(self.cli)
257    }
258    /// This API stops a user participating in a particular room.
259    ///
260    /// If the user was already in the room, they will no longer be able to see
261    /// new events in the room. If the room requires an invite to join, they
262    /// will need to be re-invited before they can re-join.
263    ///
264    /// If the user was invited to the room, but had not joined, this call
265    /// serves to reject the invite.
266    ///
267    /// The user will still be allowed to retrieve history from the room which
268    /// they were previously allowed to see.
269    pub fn leave(&mut self) -> impl Future<Item = (), Error = MatrixError> {
270        MatrixRequest::new_basic(Method::POST, format!("/rooms/{}/leave", self.room.id))
271            .discarding_send(self.cli)
272    }
273    /// This API stops a user remembering about a particular room.
274    ///
275    /// In general, history is a first class citizen in Matrix. After this API
276    /// is called, however, a user will no longer be able to retrieve history
277    /// for this room. If all users on a homeserver forget a room, the room is
278    /// eligible for deletion from that homeserver.
279    ///
280    /// If the user is currently joined to the room, they will implicitly leave
281    /// the room as part of this API call.
282    pub fn forget(&mut self) -> impl Future<Item = (), Error = MatrixError> {
283        MatrixRequest::new_basic(Method::POST, format!("/rooms/{}/forget", self.room.id))
284            .discarding_send(self.cli)
285    }
286    /// Kick a user from the room.
287    ///
288    /// The caller must have the required power level in order to perform this
289    /// operation.
290    pub fn kick_user(&mut self, user_id: &str, reason: Option<&str>) -> impl Future<Item = (), Error = MatrixError> {
291        let mut body = vec![("user_id", user_id.to_string())];
292        body.extend(reason.map(|x| ("reason", x.to_string())));
293        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/kick", self.room.id),
294                                     body)
295            .discarding_send(self.cli)
296    }
297    /// Ban a user in the room. If the user is currently in the room, also kick them.
298    ///
299    /// When a user is banned from a room, they may not join it or be invited to
300    /// it until they are unbanned.
301    ///
302    /// The caller must have the required power level in order to perform this operation.
303    pub fn ban_user(&mut self, user_id: &str, reason: Option<&str>) -> impl Future<Item = (), Error = MatrixError> {
304        let mut body = vec![("user_id", user_id.to_string())];
305        body.extend(reason.map(|x| ("reason", x.to_string())));
306        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/ban", self.room.id),
307                                     body)
308            .discarding_send(self.cli)
309    }
310    /// Unban a user from the room. This allows them to be invited to the room,
311    /// and join if they would otherwise be allowed to join according to its
312    /// join rules.
313    ///
314    /// The caller must have the required power level in order to perform this
315    /// operation.
316    pub fn unban_user(&mut self, user_id: &str) -> impl Future<Item = (), Error = MatrixError> {
317        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/unban", self.room.id),
318                                     vec![("user_id", user_id.to_string())])
319            .discarding_send(self.cli)
320    }
321    /// This API invites a user to participate in a particular room. They do not
322    /// start participating in the room until they actually join the room.
323    ///
324    /// Only users currently in a particular room can invite other users to join
325    /// that room.
326    ///
327    /// If the user was invited to the room, the homeserver will append a
328    /// m.room.member event to the room.
329    ///
330    /// Note that there are two forms of this API, which are documented
331    /// separately. This version of the API requires that the inviter knows the
332    /// Matrix identifier of the invitee. The other is documented in the third
333    /// party invites section of the Matrix spec, and is not implemented in
334    /// *Glitch in the Matrix* (yet!)
335    pub fn invite_user(&mut self, user_id: &str) -> impl Future<Item = (), Error = MatrixError> {
336        MatrixRequest::new_with_body(Method::POST, format!("/rooms/{}/invite", self.room.id),
337                                     vec![("user_id", user_id.to_string())])
338            .discarding_send(self.cli)
339    }
340    /// Get a user's power level, falling back on the default value for the room
341    /// if not present.
342    ///
343    /// The `user_id` is a `String` here, not a `&str`, because it is stored in
344    /// a future that outlives this function.
345    pub fn get_user_power_level(&mut self, user_id: String) -> impl Future<Item = u32, Error = MatrixError> {
346        let fut = self.get_typed_state::<PowerLevels>("m.room.power_levels", None);
347        Box::new(fut.map(move |x| {
348            if let Some(pl) = x.users.get(&user_id) {
349                *pl
350            }
351            else {
352                x.users_default
353            }
354        }).or_else(|e| {
355            if let MatrixError::BadRequest(ref brk) = e {
356                if brk.errcode == "M_NOT_FOUND" {
357                    return Ok(0)
358                }
359            }
360            Err(e)
361        }))
362    }
363}