Skip to content

Commit

Permalink
Notifications
Browse files Browse the repository at this point in the history
Basic notifications feature.

Does NOT check if the room of the notification is currently open to skip
showing. Thus, the feature is very annoying in its current state for most users.

Can be enabled in config.json, disabled by default because of the above
missing check.

There are no checks against sizes of message/body, usernames, or room
names.
  • Loading branch information
benjajaja committed Mar 10, 2024
1 parent b41faff commit 507b0b0
Show file tree
Hide file tree
Showing 8 changed files with 800 additions and 33 deletions.
686 changes: 654 additions & 32 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ libc = "0.2"
markup5ever_rcdom = "0.2.0"
mime = "^0.3.16"
mime_guess = "^2.0.4"
notify-rust = { version = "4.9.0", default-features = false, features = ["zbus", "serde"] }
open = "3.2.0"
rand = "0.8.5"
ratatui = "0.23"
Expand Down
3 changes: 3 additions & 0 deletions docs/iamb.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ overridden as described in *PROFILES*.
**message_user_color** (type: boolean)
> Defines whether or not the message body is colored like the username.
**notifications** (type: boolean)
> Defines wether notifications are enabled or not.
**image_preview** (type: image_preview object)
> Enable image previews and configure it. An empty object will enable the
> feature with default settings, omitting it will disable the feature.
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ pub struct TunableValues {
pub message_user_color: bool,
pub default_room: Option<String>,
pub open_command: Option<Vec<String>>,
pub notifications: bool,
pub image_preview: Option<ImagePreviewValues>,
}

Expand All @@ -496,6 +497,7 @@ pub struct Tunables {
pub message_user_color: Option<bool>,
pub default_room: Option<String>,
pub open_command: Option<Vec<String>>,
pub notifications: Option<bool>,
pub image_preview: Option<ImagePreview>,
}

Expand All @@ -518,6 +520,7 @@ impl Tunables {
message_user_color: self.message_user_color.or(other.message_user_color),
default_room: self.default_room.or(other.default_room),
open_command: self.open_command.or(other.open_command),
notifications: self.notifications.or(other.notifications),
image_preview: self.image_preview.or(other.image_preview),
}
}
Expand All @@ -538,6 +541,7 @@ impl Tunables {
message_user_color: self.message_user_color.unwrap_or(false),
default_room: self.default_room,
open_command: self.open_command,
notifications: self.notifications.unwrap_or(false),
image_preview: self.image_preview.map(ImagePreview::values),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mod commands;
mod config;
mod keybindings;
mod message;
mod notifications;
mod preview;
mod sled_export;
mod util;
Expand Down
131 changes: 131 additions & 0 deletions src/notifications.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use matrix_sdk::{
room::Room as MatrixRoom,
ruma::{
api::client::push::get_notifications::v3::Notification,
events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent},
},
Client,
};

use crate::{
base::{IambError, IambResult},
config::ApplicationSettings,
};

pub async fn register_notifications(client: &Client, settings: &ApplicationSettings) {
if !settings.tunables.notifications {
return;
}
client
.register_notification_handler(|notification, room: MatrixRoom, _: Client| {
async move {
match parse_notification(notification, room).await {
Ok((summary, body)) => {
let Some(body) = body else {
// Never show without a body.
return;
};

// TODO: never show if room is currently open.

if let Err(err) = notify_rust::Notification::new()
.summary(&summary)
.body(&body)
.appname("iamb")
.timeout(notify_rust::Timeout::Milliseconds(3000))
.action("default", "default")
.show()
{
tracing::error!("Failed to send notification: {err}")
}
},
Err(err) => {
tracing::error!("Failed to extract notification data: {err}")
},
}
}
})
.await;
return;
}

pub async fn parse_notification(
notification: Notification,
room: MatrixRoom,
) -> IambResult<(String, Option<String>)> {
let event = notification.event.deserialize().map_err(IambError::from)?;

let sender_id = event.sender();
let sender = room.get_member_no_sync(sender_id).await.map_err(IambError::from)?;

let sender_name = sender
.as_ref()
.and_then(|m| m.display_name())
.unwrap_or_else(|| sender_id.localpart());

let body = event_notification_body(
&event,
sender_name,
room.is_direct().await.map_err(IambError::from)?,
);
return Ok((sender_name.to_string(), body));
}

pub fn event_notification_body(
event: &AnySyncTimelineEvent,
sender_name: &str,
is_direct: bool,
) -> Option<String> {
let AnySyncTimelineEvent::MessageLike(event) = event else {
return None;
};

match event.original_content()? {
AnyMessageLikeEventContent::RoomMessage(message) => {
let body = match message.msgtype {
MessageType::Audio(_) => {
format!("{sender_name} sent an audio file.")
},
MessageType::Emote(content) => {
let message = &content.body;
format!("{sender_name}: {message}")
},
MessageType::File(_) => {
format!("{sender_name} sent a file.")
},
MessageType::Image(_) => {
format!("{sender_name} sent an image.")
},
MessageType::Location(_) => {
format!("{sender_name} sent their location.")
},
MessageType::Notice(content) => {
let message = &content.body;
format!("{sender_name}: {message}")
},
MessageType::ServerNotice(content) => {
let message = &content.body;
format!("{sender_name}: {message}")
},
MessageType::Text(content) => {
if is_direct {
content.body
} else {
let message = &content.body;
format!("{sender_name}: {message}")
}
},
MessageType::Video(_) => {
format!("{sender_name} sent a video.")
},
MessageType::VerificationRequest(_) => {
format!("{sender_name} sent a verification request.")
},
_ => unimplemented!(),
};
Some(body)
},
AnyMessageLikeEventContent::Sticker(_) => Some(format!("{sender_name} sent a sticker.")),
_ => None,
}
}
1 change: 1 addition & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ pub fn mock_tunables() -> TunableValues {
open_command: None,
username_display: UserDisplayStyle::Username,
message_user_color: false,
notifications: false,
image_preview: None,
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ use modalkit::errors::UIError;
use modalkit::prelude::{EditInfo, InfoMessage};

use crate::base::Need;
use crate::notifications::register_notifications;
use crate::{
base::{
AsyncProgramStore,
Expand Down Expand Up @@ -1242,12 +1243,15 @@ impl ClientWorker {

self.load_handle = tokio::spawn({
let client = self.client.clone();
let settings = self.settings.clone();

async move {
let load = load_older_forever(&client, &store);
let rcpt = send_receipts_forever(&client, &store);
let room = refresh_rooms_forever(&client, &store);
let ((), (), ()) = tokio::join!(load, rcpt, room);
let notifications = register_notifications(&client, &settings);

let ((), (), (), _) = tokio::join!(load, rcpt, room, notifications);
}
})
.into();
Expand Down

0 comments on commit 507b0b0

Please sign in to comment.