Skip to content

Commit

Permalink
Implement set/unset/show for alternative and canonical aliases (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lymkwi authored Aug 1, 2024
1 parent 9a1adfb commit 4571788
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 2 deletions.
12 changes: 12 additions & 0 deletions docs/iamb.1
Original file line number Diff line number Diff line change
Expand Up @@ -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 canon set [alias]"
Set the room's canonical alias to the one provided, and make the previous one an alternative alias.
.It Sy ":room canon unset [alias]"
Delete the room's canonical alias.
.It Sy ":room canon show"
Show the room's canonical alias, if any is set.
.El

.Sh "WINDOW COMMANDS"
Expand Down
20 changes: 20 additions & 0 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -596,6 +608,10 @@ pub enum IambError {
#[error("Invalid user identifier: {0}")]
InvalidUserId(String),

/// An invalid user identifier was specified.
#[error("Invalid room alias: {0}")]
InvalidRoomAlias(String),

/// An invalid verification identifier was specified.
#[error("Invalid verification user/device pair: {0}")]
InvalidVerificationId(String),
Expand Down Expand Up @@ -659,6 +675,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),
Expand Down
36 changes: 36 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,42 @@ 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>
("alias", "unset", Some(s)) => RoomAction::Unset(RoomField::Alias(s)).into(),
("alias", "unset", None) => return Result::Err(CommandError::InvalidArgument),

// :room aliases set <alias>
("alias", "set", Some(s)) => RoomAction::Set(RoomField::Alias(s), "".into()).into(),
("alias", "set", None) => return Result::Err(CommandError::InvalidArgument),

// :room canonicalalias show
("canonicalalias" | "canon", "show", None) => {
RoomAction::Show(RoomField::CanonicalAlias).into()
},
("canonicalalias" | "canon", "show", Some(_)) => {
return Result::Err(CommandError::InvalidArgument)
},

// :room canonicalalias set
("canonicalalias" | "canon", "set", Some(s)) => {
RoomAction::Set(RoomField::CanonicalAlias, s).into()
},
("canonicalalias" | "canon", "set", None) => {
return Result::Err(CommandError::InvalidArgument)
},

// :room canonicalalias unset
("canonicalalias" | "canon", "unset", None) => {
RoomAction::Unset(RoomField::CanonicalAlias).into()
},
("canonicalalias" | "canon", "unset", Some(_)) => {
return Result::Err(CommandError::InvalidArgument)
},

_ => return Result::Err(CommandError::InvalidArgument),
};

Expand Down
211 changes: 209 additions & 2 deletions src/windows/room/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
//! # Windows for Matrix rooms and spaces
use std::collections::HashSet;

use matrix_sdk::{
room::Room as MatrixRoom,
ruma::{
api::client::{
alias::{
create_alias::v3::Request as CreateAliasRequest,
delete_alias::v3::Request as DeleteAliasRequest,
},
error::ErrorKind as ClientApiErrorKind,
},
events::{
room::{name::RoomNameEventContent, topic::RoomTopicEventContent},
room::{
canonical_alias::RoomCanonicalAliasEventContent,
name::RoomNameEventContent,
topic::RoomTopicEventContent,
},
tag::{TagInfo, Tags},
},
OwnedEventId,
OwnedRoomAliasId,
RoomId,
},
DisplayName,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -182,7 +198,7 @@ impl RoomState {
pub async fn room_command(
&mut self,
act: RoomAction,
_: ProgramContext,
ctx: ProgramContext,
store: &mut ProgramStore,
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
match act {
Expand Down Expand Up @@ -280,6 +296,87 @@ impl RoomState {
let ev = RoomTopicEventContent::new(value);
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
},
RoomField::CanonicalAlias => {
let client = &mut store.application.worker.client;

let Ok(orai) = OwnedRoomAliasId::try_from(value.as_str()) else {
let err = IambError::InvalidRoomAlias(value);

return Err(err.into());
};

let mut alt_aliases =
room.alt_aliases().into_iter().collect::<HashSet<_>>();
let canonical_old = room.canonical_alias();

// If the room's alias is already that, ignore it
if canonical_old.as_ref() == Some(&orai) {
let msg = format!("The canonical room alias is already {orai}");

return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
}

// Try creating the room alias on the server.
let alias_create_req =
CreateAliasRequest::new(orai.clone(), room.room_id().into());
if let Err(e) = client.send(alias_create_req, None).await {
if let Some(ClientApiErrorKind::Unknown) = e.client_api_error_kind() {
// Ignore when it already exists.
} else {
return Err(IambError::from(e).into());
}
}

// Demote the previous one to an alt alias.
alt_aliases.extend(canonical_old);

// At this point the room alias definitely exists, and we can update the
// state event.
let mut ev = RoomCanonicalAliasEventContent::new();
ev.alias = Some(orai);
ev.alt_aliases = alt_aliases.into_iter().collect();
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
},
RoomField::Alias(alias) => {
let client = &mut store.application.worker.client;

let Ok(orai) = OwnedRoomAliasId::try_from(alias.as_str()) else {
let err = IambError::InvalidRoomAlias(alias);

return Err(err.into());
};

let mut alt_aliases =
room.alt_aliases().into_iter().collect::<HashSet<_>>();
let canonical = room.canonical_alias();

if alt_aliases.contains(&orai) || canonical.as_ref() == Some(&orai) {
let msg = format!("The alias {orai} already maps to this room");

return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
} else {
alt_aliases.insert(orai.clone());
}

// If the room alias does not exist on the server, create it
let alias_create_req = CreateAliasRequest::new(orai, room.room_id().into());
if let Err(e) = client.send(alias_create_req, None).await {
if let Some(ClientApiErrorKind::Unknown) = e.client_api_error_kind() {
// Ignore when it already exists.
} else {
return Err(IambError::from(e).into());
}
}

// And add it to the aliases in the state event.
let mut ev = RoomCanonicalAliasEventContent::new();
ev.alias = canonical;
ev.alt_aliases = alt_aliases.into_iter().collect();
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
},
RoomField::Aliases => {
// This never happens, aliases is only used for showing
},
}

Ok(vec![])
Expand All @@ -302,10 +399,120 @@ impl RoomState {
let ev = RoomTopicEventContent::new("".into());
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
},
RoomField::CanonicalAlias => {
let Some(alias_to_destroy) = room.canonical_alias() else {
let msg = format!("This room has no canonical alias to unset");

return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
};

// Remove the canonical alias from the state event.
let mut ev = RoomCanonicalAliasEventContent::new();
ev.alias = None;
ev.alt_aliases = room.alt_aliases();
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;

// And then unmap it on the server.
let del_req = DeleteAliasRequest::new(alias_to_destroy);
let _ = store
.application
.worker
.client
.send(del_req, None)
.await
.map_err(IambError::from)?;
},
RoomField::Alias(alias) => {
let Ok(orai) = OwnedRoomAliasId::try_from(alias.as_str()) else {
let err = IambError::InvalidRoomAlias(alias);

return Err(err.into());
};

let alt_aliases = room.alt_aliases();
let canonical = room.canonical_alias();

if !alt_aliases.contains(&orai) && canonical.as_ref() != Some(&orai) {
let msg = format!("The alias {orai:?} isn't mapped to this room");

return Ok(vec![(Action::ShowInfoMessage(msg.into()), ctx)]);
}

// Remove the alias from the state event if it's in it.
let mut ev = RoomCanonicalAliasEventContent::new();
ev.alias = canonical.filter(|canon| canon != &orai);
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)?;

// And then unmap it on the server.
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 msg = match field {
RoomField::Name => {
match room.name() {
None => "Room has no name".into(),
Some(name) => format!("Room name: {name:?}"),
}
},
RoomField::Topic => {
match room.topic() {
None => "Room has no topic".into(),
Some(topic) => format!("Room topic: {topic:?}"),
}
},
RoomField::Aliases => {
let aliases = room
.alt_aliases()
.iter()
.map(OwnedRoomAliasId::to_string)
.collect::<Vec<String>>();

if aliases.is_empty() {
"No alternative aliases in room".into()
} else {
format!("Alternative aliases: {}.", aliases.join(", "))
}
},
RoomField::CanonicalAlias => {
match room.canonical_alias() {
None => "No canonical alias for room".into(),
Some(can) => format!("Canonical alias: {can}"),
}
},
RoomField::Tag(_) => {
format!("Cannot currently show value for a tag")
},
RoomField::Alias(_) => {
format!("Cannot show a single alias; use `:room aliases show` instead.")
},
};

let msg = InfoMessage::Pager(msg);
let act = Action::ShowInfoMessage(msg);

Ok(vec![(act, ctx)])
},
}
}

Expand Down

0 comments on commit 4571788

Please sign in to comment.