diff --git a/docs/iamb.1 b/docs/iamb.1 index cba3044..c58ec9a 100644 --- a/docs/iamb.1 +++ b/docs/iamb.1 @@ -130,6 +130,18 @@ Remove a tag from the currently focused room. Set the topic of the currently focused room. .It Sy ":room topic unset" Unset the topic of the currently focused room. +.It Sy ":room alias set [alias]" +Create and point the given alias to the room. +.It Sy ":room alias unset [alias]" +Delete the provided alias from the room's alternative alias list. +.It Sy ":room alias show" +Show alternative aliases to the room, if any are set. +.It Sy ":room canonicalalias set [alias]" +Set the room's canonical alias to the one provided, deleting the previous one. +.It Sy ":room canonicalalias unset [alias]" +Delete the room's canonical alias. +.It Sy ":room canonicalalias show" +Show the room's canonical alias, if any is set. .El .Sh "WINDOW COMMANDS" diff --git a/src/base.rs b/src/base.rs index f576d34..21868f6 100644 --- a/src/base.rs +++ b/src/base.rs @@ -369,6 +369,15 @@ pub enum RoomField { /// The room topic. Topic, + + /// The room's entire list of alternative aliases. + Aliases, + + /// A specific alternative alias to the room. + Alias(String), + + /// The room's canonical alias. + CanonicalAlias, } /// An action that operates on a focused room. @@ -397,6 +406,9 @@ pub enum RoomAction { /// Unset a room property. Unset(RoomField), + + /// List the values in a list room property. + Show(RoomField), } /// An action that sends a message to a room. @@ -659,6 +671,10 @@ pub enum IambError { #[error("Unknown room identifier: {0}")] UnknownRoom(OwnedRoomId), + /// An invalid room alias id was specified. + #[error("Invalid room alias id: {0}")] + InvalidRoomAliasId(#[from] matrix_sdk::ruma::IdParseError), + /// A failure occurred during verification. #[error("Verification request error: {0}")] VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError), diff --git a/src/commands.rs b/src/commands.rs index 81fe031..b200c72 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -454,6 +454,30 @@ fn iamb_room(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { ("tag", "unset", Some(s)) => RoomAction::Unset(RoomField::Tag(tag_name(s)?)).into(), ("tag", "unset", None) => return Result::Err(CommandError::InvalidArgument), + // :room aliases show + ("alias", "show", None) => RoomAction::Show(RoomField::Aliases).into(), + ("alias", "show", Some(_)) => return Result::Err(CommandError::InvalidArgument), + + // :room aliases unset + ("alias", "unset", Some(s)) => RoomAction::Unset(RoomField::Alias(s)).into(), + ("alias", "unset", None) => return Result::Err(CommandError::InvalidArgument), + + // :room aliases set + ("alias", "set", Some(s)) => RoomAction::Set(RoomField::Alias(s), "".into()).into(), + ("alias", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room canonicalalias show + ("canonicalalias", "show", None) => RoomAction::Show(RoomField::CanonicalAlias).into(), + ("canonicalalias", "show", Some(_)) => return Result::Err(CommandError::InvalidArgument), + + // :room canonicalalias set + ("canonicalalias", "set", Some(s)) => RoomAction::Set(RoomField::CanonicalAlias, s).into(), + ("canonicalalias", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room canonicalalias unset + ("canonicalalias", "unset", None) => RoomAction::Unset(RoomField::CanonicalAlias).into(), + ("canonicalalias", "unset", Some(_)) => return Result::Err(CommandError::InvalidArgument), + _ => return Result::Err(CommandError::InvalidArgument), }; diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index e4a0abf..8f3e205 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -2,11 +2,25 @@ use matrix_sdk::{ room::Room as MatrixRoom, ruma::{ + api::client::{ + alias::{ + create_alias::v3::Request as CreateAliasRequest, + delete_alias::v3::Request as DeleteAliasRequest, + get_alias::v3::Request as GetAliasRequest, + }, + error::ErrorKind as ClientApiErrorKind, + }, events::{ - room::{name::RoomNameEventContent, topic::RoomTopicEventContent}, + room::{ + canonical_alias::RoomCanonicalAliasEventContent, + name::RoomNameEventContent, + topic::RoomTopicEventContent, + }, tag::{TagInfo, Tags}, }, OwnedEventId, + OwnedRoomAliasId, + RoomAliasId, RoomId, }, DisplayName, @@ -53,6 +67,8 @@ use crate::base::{ use self::chat::ChatState; use self::space::{Space, SpaceState}; +use std::convert::TryFrom; + mod chat; mod scrollback; mod space; @@ -182,7 +198,7 @@ impl RoomState { pub async fn room_command( &mut self, act: RoomAction, - _: ProgramContext, + ctx: ProgramContext, store: &mut ProgramStore, ) -> IambResult, ProgramContext)>> { match act { @@ -280,6 +296,85 @@ impl RoomState { let ev = RoomTopicEventContent::new(value); let _ = room.send_state_event(ev).await.map_err(IambError::from)?; }, + RoomField::CanonicalAlias => { + let mut ev = RoomCanonicalAliasEventContent::new(); + let rai: &RoomAliasId = + <&RoomAliasId>::try_from(value.as_str()).map_err(IambError::from)?; + let orai: OwnedRoomAliasId = rai.into(); + let client = &mut store.application.worker.client; + + // If the room's alias is already that, ignore it + if let Some(ex_orai) = room.canonical_alias() { + if ex_orai == orai { + return Ok(vec![]); + } + } + + // If the room alias does not exist on the server, create it + let alias_req = GetAliasRequest::new(orai.clone()); + if let Err(alias_err) = client.send(alias_req, None).await { + let errkind = alias_err.client_api_error_kind(); + if errkind.is_none() || + matches!(errkind.unwrap(), ClientApiErrorKind::NotFound) + { + // Create it + let alias_create_req = + CreateAliasRequest::new(orai.clone(), room.room_id().into()); + client + .send(alias_create_req, None) + .await + .map_err(IambError::from)?; + } + }; + + // At this point the room alias definitely exists + // There is a previous one however + let alias_to_destroy = room.canonical_alias(); + ev.alias = Some(orai); + ev.alt_aliases = room.alt_aliases(); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + if let Some(old_can) = alias_to_destroy { + let del_req = DeleteAliasRequest::new(old_can); + let _ = client.send(del_req, None).await.map_err(IambError::from)?; + } + }, + RoomField::Alias(alias) => { + let rai: &RoomAliasId = + <&RoomAliasId>::try_from(alias.as_str()).map_err(IambError::from)?; + let orai: OwnedRoomAliasId = rai.into(); + let alt_aliases = room.alt_aliases(); + if !alt_aliases.contains(&orai) { + let client = &mut store.application.worker.client; + let mut ev = RoomCanonicalAliasEventContent::new(); + ev.alias = room.canonical_alias(); + ev.alt_aliases = alt_aliases; + ev.alt_aliases.push(orai.clone()); + + // If the room alias does not exist on the server, create it + let alias_req = GetAliasRequest::new(orai.clone()); + if let Err(alias_err) = client.send(alias_req, None).await { + let errkind = alias_err.client_api_error_kind(); + if errkind.is_none() || + matches!(errkind.unwrap(), ClientApiErrorKind::NotFound) + { + // Create it + let alias_create_req = CreateAliasRequest::new( + orai.clone(), + room.room_id().into(), + ); + client + .send(alias_create_req, None) + .await + .map_err(IambError::from)?; + } + }; + + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + } + }, + RoomField::Aliases => { + // This never happens, aliases is only used for showing + }, } Ok(vec![]) @@ -302,10 +397,99 @@ impl RoomState { let ev = RoomTopicEventContent::new("".into()); let _ = room.send_state_event(ev).await.map_err(IambError::from)?; }, + RoomField::CanonicalAlias => { + let mut ev = RoomCanonicalAliasEventContent::new(); + ev.alias = None; + ev.alt_aliases = room.alt_aliases(); + let alias_to_destroy = room.canonical_alias(); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + if let Some(old_can) = alias_to_destroy { + let del_req = DeleteAliasRequest::new(old_can); + let _ = store + .application + .worker + .client + .send(del_req, None) + .await + .map_err(IambError::from)?; + } + }, + RoomField::Alias(alias) => { + let rai: &RoomAliasId = + <&RoomAliasId>::try_from(alias.as_str()).map_err(IambError::from)?; + let orai: OwnedRoomAliasId = rai.into(); + let alt_aliases = room.alt_aliases(); + if alt_aliases.contains(&orai) { + let mut ev = RoomCanonicalAliasEventContent::new(); + ev.alias = room.canonical_alias(); + ev.alt_aliases = alt_aliases; + ev.alt_aliases.retain(|in_orai| *in_orai != orai); + let _ = room.send_state_event(ev).await.map_err(IambError::from)?; + let del_req = DeleteAliasRequest::new(orai); + let _ = store + .application + .worker + .client + .send(del_req, None) + .await + .map_err(IambError::from)?; + } + }, + RoomField::Aliases => { + // This will not happen, you cannot unset all aliases + }, } Ok(vec![]) }, + RoomAction::Show(field) => { + let room = store + .application + .get_joined_room(self.id()) + .ok_or(UIError::Application(IambError::NotJoined))?; + + let action; + match field { + RoomField::Name => { + action = InfoMessage::Message(match room.name() { + None => "Room has no name".into(), + Some(name) => format!("Room name: \"{name}\"."), + }); + }, + RoomField::Topic => { + action = InfoMessage::Message(match room.topic() { + None => "Room has no topic".into(), + Some(topic) => format!("Room topic: \"{topic}\"."), + }); + }, + RoomField::Aliases => { + let aliases = room.alt_aliases(); + action = InfoMessage::Message(match aliases.is_empty() { + true => "No alternative aliases in room.".into(), + false => { + format!( + "Alternative aliases: {}.", + aliases + .iter() + .map(OwnedRoomAliasId::to_string) + .collect::>() + .join(", ") + ) + }, + }) + }, + RoomField::CanonicalAlias => { + action = InfoMessage::Message(match room.canonical_alias() { + None => "No canonical alias for room.".into(), + Some(can) => format!("Canonical alias: {can}."), + }) + }, + RoomField::Tag(_) | RoomField::Alias(_) => { + unreachable!("Nothing should ever enter this code path"); + }, + } + Ok(vec![(Action::ShowInfoMessage(action), ctx)]) + }, } }