From c69d54e2d4ac4234d183aeda8515410c3ee17e71 Mon Sep 17 00:00:00 2001 From: Devin R Date: Fri, 10 Apr 2020 16:32:28 -0400 Subject: [PATCH] async_client: impl kick, join, leave, invite, create for --- src/async_client.rs | 167 ++++++++++++++++++++++++++++++++- src/lib.rs | 2 + src/request_builder.rs | 204 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 src/request_builder.rs diff --git a/src/async_client.rs b/src/async_client.rs index 9ce12c68..282e0c35 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -37,7 +37,7 @@ use ruma_api::{Endpoint, Outgoing}; use ruma_events::room::message::MessageEventContent; use ruma_events::EventResult; pub use ruma_events::EventType; -use ruma_identifiers::RoomId; +use ruma_identifiers::{RoomId, RoomIdOrAliasId, UserId}; #[cfg(feature = "encryption")] use ruma_identifiers::{DeviceId, UserId}; @@ -181,7 +181,16 @@ impl SyncSettings { use api::r0::client_exchange::send_event_to_device; #[cfg(feature = "encryption")] use api::r0::keys::{claim_keys, get_keys, upload_keys, KeyAlgorithm}; +use api::r0::membership::join_room_by_id; +use api::r0::membership::join_room_by_id_or_alias; +use api::r0::membership::kick_user; +use api::r0::membership::leave_room; +use api::r0::membership::{ + invite_user::{self, InvitationRecipient}, + Invite3pid, +}; use api::r0::message::create_message_event; +use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; @@ -333,6 +342,162 @@ impl AsyncClient { Ok(response) } + /// Join a room by `RoomId`. + /// + /// Returns a `join_room_by_id::Response` consisting of the + /// joined rooms `RoomId`. + /// + /// # Arguments + /// + /// * room_id - A valid RoomId otherwise sending will fail. + /// + pub async fn join_room_by_id(&mut self, room_id: &RoomId) -> Result { + let request = join_room_by_id::Request { + room_id: room_id.clone(), + third_party_signed: None, + }; + self.send(request).await + } + + /// Join a room by `RoomId`. + /// + /// Returns a `join_room_by_id_or_alias::Response` consisting of the + /// joined rooms `RoomId`. + /// + /// # Arguments + /// + /// * alias - A valid `RoomIdOrAliasId` otherwise sending will fail. + /// + pub async fn join_room_by_id_or_alias( + &mut self, + alias: &RoomIdOrAliasId, + ) -> Result { + let request = join_room_by_id_or_alias::Request { + room_id_or_alias: alias.clone(), + third_party_signed: None, + }; + self.send(request).await + } + + /// Kick a user out of the specified room. + /// + /// Returns a `kick_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId` otherwise sending will fail. + /// + /// * user_id - A valid `UserId`. + /// + /// * reason - Optional reason why the room member is being kicked out. + /// + pub async fn kick_user( + &mut self, + room_id: &RoomId, + user_id: &UserId, + reason: Option, + ) -> Result { + let request = kick_user::Request { + reason, + room_id: room_id.clone(), + user_id: user_id.clone(), + }; + self.send(request).await + } + + /// Leave the specified room. + /// + /// Returns a `leave_room::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + pub async fn leave_room(&mut self, room_id: &RoomId) -> Result { + let request = leave_room::Request { + room_id: room_id.clone(), + }; + self.send(request).await + } + + /// Invite the specified user by `UserId` to the given room. + /// + /// Returns a `invite_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + /// * user_id - A valid `UserId`. + /// + pub async fn invite_user_by_id( + &mut self, + room_id: &RoomId, + user_id: &UserId, + ) -> Result { + let request = invite_user::Request { + room_id: room_id.clone(), + recipient: InvitationRecipient::UserId { + user_id: user_id.clone(), + }, + }; + self.send(request).await + } + + /// Invite the specified user by third party id to the given room. + /// + /// Returns a `invite_user::Response`, an empty response. + /// + /// # Arguments + /// + /// * room_id - A valid `RoomId`. + /// + /// * invite_id - A valid `UserId`. + /// + pub async fn invite_user_by_3pid( + &mut self, + room_id: &RoomId, + invite_id: &Invite3pid, + ) -> Result { + let request = invite_user::Request { + room_id: room_id.clone(), + recipient: InvitationRecipient::ThirdPartyId(invite_id.clone()), + }; + self.send(request).await + } + + /// A builder to create a room and send the request. + /// + /// Returns a `create_room::Response`, an empty response. + /// + /// # Arguments + /// + /// * room - the easiest way to create this request is using `RoomBuilder` struct. + /// + /// # Examples + /// ```ignore + /// use matrix_sdk::{AsyncClient, RoomBuilder}; + /// + /// let mut bldr = RoomBuilder::default(); + /// bldr.creation_content(false) + /// .initial_state(vec![]) + /// .visibility(Visibility::Public) + /// .name("name") + /// .room_version("v1.0"); + /// + /// let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + /// + /// assert!(cli.create_room(bldr).await.is_ok()); + /// ``` + /// + pub async fn create_room>( + &mut self, + room: R, + ) -> Result { + let request = room.into(); + self.send(request).await + } + /// Synchronize the client's state with the latest state on the server. /// /// # Arguments diff --git a/src/lib.rs b/src/lib.rs index 28bf9375..3cbf4379 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ mod base_client; mod error; mod event_emitter; mod models; +mod request_builder; mod session; #[cfg(test)] @@ -50,5 +51,6 @@ pub use async_client::{AsyncClient, AsyncClientConfig, SyncSettings}; pub use base_client::Client; pub use event_emitter::EventEmitter; pub use models::Room; +pub use request_builder::RoomBuilder; pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/request_builder.rs b/src/request_builder.rs new file mode 100644 index 00000000..aed4f55e --- /dev/null +++ b/src/request_builder.rs @@ -0,0 +1,204 @@ +use crate::events::room::power_levels::PowerLevelsEventContent; +use crate::identifiers::UserId; +use crate::api; +use api::r0::membership::Invite3pid; +use api::r0::room::{ + create_room::{self, CreationContent, InitialStateEvent, RoomPreset}, + Visibility, +}; + +/// A builder used to create rooms. +/// +/// # Examples +/// ``` +/// +/// ``` +#[derive(Default)] +pub struct RoomBuilder { + /// Extra keys to be added to the content of the `m.room.create`. + creation_content: Option, + /// List of state events to send to the new room. + /// + /// Takes precedence over events set by preset, but gets overriden by + /// name and topic keys. + initial_state: Vec, + /// A list of user IDs to invite to the room. + /// + /// This will tell the server to invite everyone in the list to the newly created room. + invite: Vec, + /// List of third party IDs of users to invite. + invite_3pid: Vec, + /// If set, this sets the `is_direct` flag on room invites. + is_direct: Option, + /// If this is included, an `m.room.name` event will be sent into the room to indicate + /// the name of the room. + name: Option, + /// Power level content to override in the default power level event. + power_level_content_override: Option, + /// Convenience parameter for setting various default state events based on a preset. + preset: Option, + /// The desired room alias local part. + room_alias_name: Option, + /// Room version to set for the room. Defaults to homeserver's default if not specified. + room_version: Option, + /// If this is included, an `m.room.topic` event will be sent into the room to indicate + /// the topic for the room. + topic: Option, + /// A public visibility indicates that the room will be shown in the published room + /// list. A private visibility will hide the room from the published room list. Rooms + /// default to private visibility if this key is not included. + visibility: Option, +} + +impl RoomBuilder { + /// Returns an empty `RoomBuilder` for creating rooms. + pub fn new() -> Self { + Self::default() + } + + /// Set the `CreationContent`. + /// + /// Weather users on other servers can join this room. + pub fn creation_content(&mut self, federate: bool) -> &mut Self { + let federate = Some(federate); + self.creation_content = Some(CreationContent { federate }); + self + } + + /// Set the `InitialStateEvent` vector. + /// + pub fn initial_state(&mut self, state: Vec) -> &mut Self { + self.initial_state = state; + self + } + + /// Set the vec of `UserId`s. + /// + pub fn invite(&mut self, invite: Vec) -> &mut Self { + self.invite = invite; + self + } + + /// Set the vec of `Invite3pid`s. + /// + pub fn invite_3pid(&mut self, invite: Vec) -> &mut Self { + self.invite_3pid = invite; + self + } + + /// Set the vec of `Invite3pid`s. + /// + pub fn is_direct(&mut self, direct: bool) -> &mut Self { + self.is_direct = Some(direct); + self + } + + /// Set the room name. A `m.room.name` event will be sent to the room. + /// + pub fn name>(&mut self, name: S) -> &mut Self { + self.name = Some(name.into()); + self + } + + /// Set the room's power levels. + /// + pub fn power_level_override(&mut self, power: PowerLevelsEventContent) -> &mut Self { + self.power_level_content_override = Some(power); + self + } + + /// Convenience for setting various default state events based on a preset. + /// + pub fn preset(&mut self, preset: RoomPreset) -> &mut Self { + self.preset = Some(preset); + self + } + + /// The local part of a room alias. + /// + pub fn room_alias_name>(&mut self, alias: S) -> &mut Self { + self.room_alias_name = Some(alias.into()); + self + } + + /// Room version, defaults to homeserver's version if left unspecified. + /// + pub fn room_version>(&mut self, version: S) -> &mut Self { + self.room_version = Some(version.into()); + self + } + + /// If included, a `m.room.topic` event will be sent to the room. + /// + pub fn topic>(&mut self, topic: S) -> &mut Self { + self.topic = Some(topic.into()); + self + } + + /// A public visibility indicates that the room will be shown in the published + /// room list. A private visibility will hide the room from the published room list. + /// Rooms default to private visibility if this key is not included. + /// + pub fn visibility(&mut self, vis: Visibility) -> &mut Self { + self.visibility = Some(vis); + self + } +} + +impl Into for RoomBuilder { + fn into(self) -> create_room::Request { + create_room::Request { + creation_content: self.creation_content, + initial_state: self.initial_state, + invite: self.invite, + invite_3pid: self.invite_3pid, + is_direct: self.is_direct, + name: self.name, + power_level_content_override: self.power_level_content_override, + preset: self.preset, + room_alias_name: self.room_alias_name, + room_version: self.room_version, + topic: self.topic, + visibility: self.visibility, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::{AsyncClient, Session}; + + use mockito::mock; + use url::Url; + + use std::convert::TryFrom; + + #[tokio::test] + async fn create_room_builder() { + let homeserver = Url::parse(&mockito::server_url()).unwrap(); + + let _m = mock("POST", "/_matrix/client/r0/createRoom") + .with_status(200) + .with_body_from_file("./tests/data/room_id.json") + .create(); + + let session = Session { + access_token: "1234".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let mut bldr = RoomBuilder::default(); + bldr.creation_content(false) + .initial_state(vec![]) + .visibility(Visibility::Public) + .name("name") + .room_version("v1.0"); + + let mut cli = AsyncClient::new(homeserver, Some(session)).unwrap(); + + assert!(cli.create_room(bldr).await.is_ok()); + } +}