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