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}