From 2a664969132f457824e6c2c90e4f25b6558be6ec Mon Sep 17 00:00:00 2001 From: Tony <11652273+rmsthebest@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:43:19 +0200 Subject: [PATCH] Add command to set per-room notification levels (#305) --- docs/iamb.1 | 19 +++++++++++ src/base.rs | 10 ++++++ src/commands.rs | 35 +++++++++++++++++++- src/windows/room/mod.rs | 72 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/docs/iamb.1 b/docs/iamb.1 index bdf1744..8eb8e45 100644 --- a/docs/iamb.1 +++ b/docs/iamb.1 @@ -122,6 +122,25 @@ View a list of members of the currently focused room. Set the name of the currently focused room. .It Sy ":room name unset" Unset the name of the currently focused room. +.It Sy ":room notify set [level]" +Set a notification level for the currently focused room. +Valid levels are +.Dq mute , +.Dq mentions , +.Dq keywords , +and +.Dq all . +Note that +.Dq mentions +and +.Dq keywords +are aliases for the same behaviour. +.It Sy ":room notify unset" +Unset any room-level notification configuration. +.It Sy ":room notify show" +Show the current room-level notification configuration. +If the room is using the account-level default, then this will print +.Dq default . .It Sy ":room tag set [tag]" Add a tag to the currently focused room. .It Sy ":room tag unset [tag]" diff --git a/src/base.rs b/src/base.rs index 3a3d1e7..2557729 100644 --- a/src/base.rs +++ b/src/base.rs @@ -378,6 +378,9 @@ pub enum RoomField { /// The room topic. Topic, + /// Notification level. + NotificationMode, + /// The room's entire list of alternative aliases. Aliases, @@ -612,6 +615,10 @@ pub type MessageReactions = HashMap; /// Errors encountered during application use. #[derive(thiserror::Error, Debug)] pub enum IambError { + /// An invalid notification level was specified. + #[error("Invalid notification level: {0}")] + InvalidNotificationLevel(String), + /// An invalid user identifier was specified. #[error("Invalid user identifier: {0}")] InvalidUserId(String), @@ -691,6 +698,9 @@ pub enum IambError { #[error("Verification request error: {0}")] VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError), + #[error("Notification setting error: {0}")] + NotificationSettingError(#[from] matrix_sdk::NotificationSettingsError), + /// A failure related to images. #[error("Image error: {0}")] Image(#[from] image::ImageError), diff --git a/src/commands.rs b/src/commands.rs index c287756..3a80a50 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -34,7 +34,7 @@ type ProgResult = CommandResult; /// Convert strings the user types into a tag name. fn tag_name(name: String) -> Result { - let tag = match name.as_str() { + let tag = match name.to_lowercase().as_str() { "fav" | "favorite" | "favourite" | "m.favourite" => TagName::Favorite, "low" | "lowpriority" | "low_priority" | "low-priority" | "m.lowpriority" => { TagName::LowPriority @@ -431,6 +431,18 @@ fn iamb_room(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult { ("tag", "set", Some(s)) => RoomAction::Set(RoomField::Tag(tag_name(s)?), "".into()).into(), ("tag", "set", None) => return Result::Err(CommandError::InvalidArgument), + // :room notify set + ("notify", "set", Some(s)) => RoomAction::Set(RoomField::NotificationMode, s).into(), + ("notify", "set", None) => return Result::Err(CommandError::InvalidArgument), + + // :room notify unset + ("notify", "unset", None) => RoomAction::Unset(RoomField::NotificationMode).into(), + ("notify", "unset", Some(_)) => return Result::Err(CommandError::InvalidArgument), + + // :room notify show + ("notify", "show", None) => RoomAction::Show(RoomField::NotificationMode).into(), + ("notify", "show", Some(_)) => return Result::Err(CommandError::InvalidArgument), + // :room tag unset ("tag", "unset", Some(s)) => RoomAction::Unset(RoomField::Tag(tag_name(s)?)).into(), ("tag", "unset", None) => return Result::Err(CommandError::InvalidArgument), @@ -977,6 +989,27 @@ mod tests { ); } + #[test] + fn test_cmd_room_notification_mode_set() { + let mut cmds = setup_commands(); + let ctx = EditContext::default(); + + let cmd = format!("room notify set mute"); + let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap(); + let act = RoomAction::Set(RoomField::NotificationMode, "mute".into()); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let cmd = format!("room notify unset"); + let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap(); + let act = RoomAction::Unset(RoomField::NotificationMode); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + + let cmd = format!("room notify show"); + let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap(); + let act = RoomAction::Show(RoomField::NotificationMode); + assert_eq!(res, vec![(act.into(), ctx.clone())]); + } + #[test] fn test_cmd_invite() { let mut cmds = setup_commands(); diff --git a/src/windows/room/mod.rs b/src/windows/room/mod.rs index 7288678..5c25bfb 100644 --- a/src/windows/room/mod.rs +++ b/src/windows/room/mod.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use matrix_sdk::{ + notification_settings::RoomNotificationMode, room::Room as MatrixRoom, ruma::{ api::client::{ @@ -82,6 +83,19 @@ macro_rules! delegate { }; } +fn notification_mode(name: impl Into) -> IambResult { + let name = name.into(); + + let mode = match name.to_lowercase().as_str() { + "mute" => RoomNotificationMode::Mute, + "mentions" | "keywords" => RoomNotificationMode::MentionsAndKeywordsOnly, + "all" => RoomNotificationMode::AllMessages, + _ => return Err(IambError::InvalidNotificationLevel(name).into()), + }; + + Ok(mode) +} + /// State for a Matrix room or space. /// /// Since spaces function as special rooms within Matrix, we wrap their window state together, so @@ -296,6 +310,16 @@ impl RoomState { let ev = RoomTopicEventContent::new(value); let _ = room.send_state_event(ev).await.map_err(IambError::from)?; }, + RoomField::NotificationMode => { + let mode = notification_mode(value)?; + let client = &store.application.worker.client; + let notifications = client.notification_settings().await; + + notifications + .set_room_notification_mode(self.id(), mode) + .await + .map_err(IambError::from)?; + }, RoomField::CanonicalAlias => { let client = &mut store.application.worker.client; @@ -399,6 +423,15 @@ impl RoomState { let ev = RoomTopicEventContent::new("".into()); let _ = room.send_state_event(ev).await.map_err(IambError::from)?; }, + RoomField::NotificationMode => { + let client = &store.application.worker.client; + let notifications = client.notification_settings().await; + + notifications + .delete_user_defined_room_rules(self.id()) + .await + .map_err(IambError::from)?; + }, RoomField::CanonicalAlias => { let Some(alias_to_destroy) = room.canonical_alias() else { let msg = "This room has no canonical alias to unset"; @@ -481,6 +514,21 @@ impl RoomState { Some(topic) => format!("Room topic: {topic:?}"), } }, + RoomField::NotificationMode => { + let client = &store.application.worker.client; + let notifications = client.notification_settings().await; + let mode = + notifications.get_user_defined_room_notification_mode(self.id()).await; + + let level = match mode { + Some(RoomNotificationMode::Mute) => "mute", + Some(RoomNotificationMode::MentionsAndKeywordsOnly) => "keywords", + Some(RoomNotificationMode::AllMessages) => "all", + None => "default", + }; + + format!("Room notification level: {level:?}") + }, RoomField::Aliases => { let aliases = room .alt_aliases() @@ -677,3 +725,27 @@ impl WindowOps for RoomState { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_room_notification_level() { + let tests = vec![ + ("mute", RoomNotificationMode::Mute), + ("mentions", RoomNotificationMode::MentionsAndKeywordsOnly), + ("keywords", RoomNotificationMode::MentionsAndKeywordsOnly), + ("all", RoomNotificationMode::AllMessages), + ]; + + for (input, expect) in tests { + let res = notification_mode(input).unwrap(); + assert_eq!(expect, res); + } + + assert!(notification_mode("invalid").is_err()); + assert!(notification_mode("not a level").is_err()); + assert!(notification_mode("@user:example.com").is_err()); + } +}