diff --git a/crates/core/src/room/service.rs b/crates/core/src/room/service.rs index 0078daa..cf82da4 100644 --- a/crates/core/src/room/service.rs +++ b/crates/core/src/room/service.rs @@ -4,7 +4,7 @@ use tracing::instrument; use validator::Validate; use matrix::client::resources::room::{ - CreateRoomCreationContent, CreateRoomRequestBody, Room as MatrixRoom, RoomPreset, + Room as MatrixRoom, RoomPreset, RoomCreationContent, CreateRoomBody, }; use matrix::Client as MatrixAdminClient; @@ -46,12 +46,13 @@ impl RoomService { match MatrixRoom::create( &self.admin, access_token.to_string(), - CreateRoomRequestBody { - creation_content: CreateRoomCreationContent { m_federate: false }, + CreateRoomBody { + creation_content: Some(RoomCreationContent { federate: false }), + preset: Some(RoomPreset::PublicChat), name: dto.name, - preset: RoomPreset::PublicChat, room_alias_name: dto.alias, topic: dto.topic, + ..Default::default() }, ) .await diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 98c8fbb..574fd64 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -11,6 +11,9 @@ hmac = "0.12.1" serde_path_to_error = "0.1.14" serde_qs = "0.12.0" sha1 = "0.10.6" +ruma-events = { version = "0.27.11", features = ["html", "markdown"] } +ruma-common = { version = "0.12.1", features = ["rand"] } +ruma-macros = "0.12.0" # Workspace Dependencies anyhow = { workspace = true } diff --git a/crates/matrix/src/client/resources/room.rs b/crates/matrix/src/client/resources/room.rs index b732718..f8c2e83 100644 --- a/crates/matrix/src/client/resources/room.rs +++ b/crates/matrix/src/client/resources/room.rs @@ -1,10 +1,10 @@ use anyhow::Result; +use ruma_common::{serde::Raw, OwnedUserId, RoomOrAliasId, RoomId}; +use ruma_events::{AnyInitialStateEvent, room::power_levels::RoomPowerLevelsEventContent}; use serde::{Deserialize, Serialize}; use tracing::instrument; -use crate::admin::resources::user_id::UserId; - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum RoomPreset { PrivateChat, @@ -12,38 +12,95 @@ pub enum RoomPreset { TrustedPrivateChat, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CreateRoomCreationContent { +#[derive(Clone, Debug, Default, Serialize)] +pub struct RoomCreationContent { #[serde(rename = "m.federate")] - pub m_federate: bool, + pub federate: bool, +} + +#[derive(Clone, Debug, Default, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum RoomVisibility { + Public, + #[default] + Private, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CreateRoomRequestBody { - pub creation_content: CreateRoomCreationContent, +#[derive(Clone, Debug, Default, Serialize)] +pub struct CreateRoomBody { + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub initial_state: Vec>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub creation_content: Option, + + #[serde(default, skip_serializing_if = "<[_]>::is_empty")] + pub invite: Vec, + + pub is_direct: bool, + + #[serde(skip_serializing_if = "String::is_empty")] pub name: String, - pub preset: RoomPreset, + + #[serde(skip_serializing_if = "Option::is_none")] + pub power_override: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub preset: Option, + + #[serde(skip_serializing_if = "String::is_empty")] pub room_alias_name: String, + + #[serde(skip_serializing_if = "String::is_empty")] pub topic: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub visibility: Option, +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct JoinRoomBody { + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[derive(Clone, Debug, Default, Serialize)] +pub struct ForgetRoomBody { + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct LeaveRoomBody { + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CreateRoomResponseBody { +#[derive(Clone, Debug, Serialize)] +pub struct RoomKickOrBanBody { + #[serde(skip_serializing_if = "String::is_empty")] + pub reason: String, + + pub user_id: OwnedUserId, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct CreateRoomResponse { pub room_id: String, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize)] +pub struct JoinRoomResponse { + pub room_id: String, +} + +#[derive(Clone, Debug, Deserialize)] pub struct RoomApiError { pub errcode: String, pub error: String, } -#[derive(Debug, Serialize, Deserialize)] -pub struct Room { - pub device_id: String, - pub is_guest: bool, - pub user_id: UserId, -} +pub struct Room; impl Room { /// Create a new room with various configuration options. @@ -53,13 +110,194 @@ impl Room { pub async fn create( client: &crate::http::Client, access_token: impl Into, - creation_content: CreateRoomRequestBody, - ) -> Result { + body: CreateRoomBody, + ) -> Result { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json("/_matrix/client/v3/createRoom", &body) + .await?; + + if resp.status().is_success() { + return Ok(resp.json().await?); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Join a particular room, if we are allowed to participate. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#joining-rooms + #[instrument(skip(client, access_token))] + pub async fn join( + client: &crate::http::Client, + access_token: impl Into, + alias_or_id: &RoomOrAliasId, + body: JoinRoomBody, + ) -> Result { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json( + format!("/_matrix/client/v3/join/{alias_or_id}"), + &body, + ) + .await?; + + if resp.status().is_success() { + return Ok(resp.json().await?); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Forget a particular room. + /// This will prevent the user from accessing the history of the room. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms + #[instrument(skip(client, access_token))] + pub async fn forget( + client: &crate::http::Client, + access_token: impl Into, + room_id: &RoomId, + body: ForgetRoomBody, + ) -> Result<()> { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json( + format!("/_matrix/client/v3/rooms/{room_id}/forget"), + &body, + ) + .await?; + + if resp.status().is_success() { + return Ok(()); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Leave a particular room. + /// They are still allowed to retrieve the history which they were previously allowed to see. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms + #[instrument(skip(client, access_token))] + pub async fn leave( + client: &crate::http::Client, + access_token: impl Into, + room_id: &RoomId, + body: LeaveRoomBody, + ) -> Result<()> { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json( + format!("/_matrix/client/v3/rooms/{room_id}/leave"), + &body, + ) + .await?; + + if resp.status().is_success() { + return Ok(resp.json().await?); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Kick a user from a particular room. + /// The caller must have the required power level in order to perform this operation. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms + #[instrument(skip(client, access_token))] + pub async fn kick( + client: &crate::http::Client, + access_token: impl Into, + room_id: &RoomId, + body: RoomKickOrBanBody, + ) -> Result<()> { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json( + format!("/_matrix/client/v3/rooms/{room_id}/kick"), + &body, + ) + .await?; + + if resp.status().is_success() { + return Ok(resp.json().await?); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Ban a user from a particular room. + /// This will kick them too if they are still a member. + /// The caller must have the required power level in order to perform this operation. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#leaving-rooms + #[instrument(skip(client, access_token))] + pub async fn ban( + client: &crate::http::Client, + access_token: impl Into, + room_id: &RoomId, + body: RoomKickOrBanBody, + ) -> Result<()> { + let mut tmp = (*client).clone(); + tmp.set_token(access_token)?; + + let resp = tmp + .post_json( + format!("/_matrix/client/v3/rooms/{room_id}/ban"), + &body, + ) + .await?; + + if resp.status().is_success() { + return Ok(resp.json().await?); + } + + let error = resp.json::().await?; + + Err(anyhow::anyhow!(error.error)) + } + + /// Unban a user from a particular room. + /// This will allow them to re-join or be re-invited. + /// The caller must have the required power level in order to perform this operation. + /// + /// Refer: https://spec.matrix.org/v1.9/client-server-api/#banning-users-in-a-room + #[instrument(skip(client, access_token))] + pub async fn unban( + client: &crate::http::Client, + access_token: impl Into, + room_id: &RoomId, + body: RoomKickOrBanBody, + ) -> Result<()> { let mut tmp = (*client).clone(); tmp.set_token(access_token)?; let resp = tmp - .post_json("/_matrix/client/v3/createRoom", &creation_content) + .post_json( + format!("/_matrix/client/v3/rooms/{room_id}/unban"), + &body, + ) .await?; if resp.status().is_success() { diff --git a/crates/test/src/matrix/mod.rs b/crates/test/src/matrix/mod.rs index 9e56f51..8767c31 100644 --- a/crates/test/src/matrix/mod.rs +++ b/crates/test/src/matrix/mod.rs @@ -1 +1,2 @@ mod shared_token_registration; +mod room;