Ring bell when mentioned

This commit is contained in:
Joscha 2025-03-08 19:21:01 +01:00
parent 496cdde18d
commit a17630aeaa
6 changed files with 65 additions and 9 deletions

View file

@ -15,6 +15,10 @@ Procedure when bumping the version number:
## Unreleased ## Unreleased
### Added
- `bell_on_mention` config option
## v0.9.1 - 2025-03-01 ## v0.9.1 - 2025-03-01
### Fixed ### Fixed

4
Cargo.lock generated
View file

@ -1646,8 +1646,8 @@ dependencies = [
[[package]] [[package]]
name = "toss" name = "toss"
version = "0.3.3" version = "0.3.4"
source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.3#96b2e13c4a4b0174601d90246d92d148c4230eeb" source = "git+https://github.com/Garmelon/toss.git?tag=v0.3.4#57aa8c59308f6f0aa82bde415a42b56c3d6f7c4d"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"crossterm", "crossterm",

View file

@ -39,7 +39,7 @@ features = ["bot"]
[workspace.dependencies.toss] [workspace.dependencies.toss]
git = "https://github.com/Garmelon/toss.git" git = "https://github.com/Garmelon/toss.git"
tag = "v0.3.3" tag = "v0.3.4"
[workspace.dependencies.vault] [workspace.dependencies.vault]
git = "https://github.com/Garmelon/vault.git" git = "https://github.com/Garmelon/vault.git"

View file

@ -100,6 +100,10 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub rooms_sort_order: RoomsSortOrder, pub rooms_sort_order: RoomsSortOrder,
/// Ring the bell (character 0x07) when you are mentioned in a room.
#[serde(default)]
pub bell_on_mention: bool,
/// Time zone that chat timestamps should be displayed in. /// Time zone that chat timestamps should be displayed in.
/// ///
/// This option can either be the string `"localtime"`, a [POSIX TZ string], /// This option can either be the string `"localtime"`, a [POSIX TZ string],

View file

@ -4,8 +4,8 @@ use cove_config::{Config, Keys};
use cove_input::InputEvent; use cove_input::InputEvent;
use crossterm::style::Stylize; use crossterm::style::Stylize;
use euphoxide::{ use euphoxide::{
api::{Data, Message, MessageId, PacketType, SessionId}, api::{Data, Message, MessageId, PacketType, SessionId, packet::ParsedPacket},
bot::instance::{Event, ServerConfig}, bot::instance::{ConnSnapshot, Event, ServerConfig},
conn::{self, Joined, Joining, SessionInfo}, conn::{self, Joined, Joining, SessionInfo},
}; };
use jiff::tz::TimeZone; use jiff::tz::TimeZone;
@ -19,7 +19,7 @@ use toss::{
}; };
use crate::{ use crate::{
euph, euph::{self, SpanType},
macros::logging_unwrap, macros::logging_unwrap,
ui::{ ui::{
UiError, UiEvent, UiError, UiEvent,
@ -73,6 +73,8 @@ pub struct EuphRoom {
last_msg_sent: Option<oneshot::Receiver<MessageId>>, last_msg_sent: Option<oneshot::Receiver<MessageId>>,
nick_list: ListState<SessionId>, nick_list: ListState<SessionId>,
mentioned: bool,
} }
impl EuphRoom { impl EuphRoom {
@ -96,6 +98,7 @@ impl EuphRoom {
chat: ChatState::new(vault, tz), chat: ChatState::new(vault, tz),
last_msg_sent: None, last_msg_sent: None,
nick_list: ListState::new(), nick_list: ListState::new(),
mentioned: false,
} }
} }
@ -164,6 +167,12 @@ impl EuphRoom {
} }
} }
pub fn retrieve_mentioned(&mut self) -> bool {
let mentioned = self.mentioned;
self.mentioned = false;
mentioned
}
pub async fn unseen_msgs_count(&self) -> usize { pub async fn unseen_msgs_count(&self) -> usize {
logging_unwrap!(self.vault().unseen_msgs_count().await) logging_unwrap!(self.vault().unseen_msgs_count().await)
} }
@ -557,6 +566,35 @@ impl EuphRoom {
return false; return false;
} }
if let Event::Packet(
_,
ParsedPacket {
content: Ok(Data::SendEvent(send)),
..
},
ConnSnapshot {
state: conn::State::Joined(joined),
..
},
) = &event
{
let normalized_name = euphoxide::nick::normalize(&joined.session.name);
let content = &*send.0.content;
for (rtype, rspan) in euph::find_spans(content) {
if rtype != SpanType::Mention {
continue;
}
let Some(mention) = content[rspan].strip_prefix('@') else {
continue;
};
let normalized_mention = euphoxide::nick::normalize(mention);
if normalized_name == normalized_mention {
self.mentioned = true;
break;
}
}
}
// We handle the packet internally first because the room event handling // We handle the packet internally first because the room event handling
// will consume it while we only need a reference. // will consume it while we only need a reference.
let handled = if let Event::Packet(_, packet, _) = &event { let handled = if let Event::Packet(_, packet, _) = &event {

View file

@ -17,7 +17,7 @@ use jiff::tz::TimeZone;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use toss::{ use toss::{
Style, Styled, Widget, WidgetExt, Style, Styled, Widget, WidgetExt,
widgets::{BoxedAsync, Empty, Join2, Text}, widgets::{BellState, BoxedAsync, Empty, Join2, Text},
}; };
use crate::{ use crate::{
@ -95,6 +95,7 @@ pub struct Rooms {
list: ListState<RoomIdentifier>, list: ListState<RoomIdentifier>,
order: Order, order: Order,
bell: BellState,
euph_servers: HashMap<String, EuphServer>, euph_servers: HashMap<String, EuphServer>,
euph_rooms: HashMap<RoomIdentifier, EuphRoom>, euph_rooms: HashMap<RoomIdentifier, EuphRoom>,
@ -115,6 +116,7 @@ impl Rooms {
state: State::ShowList, state: State::ShowList,
list: ListState::new(), list: ListState::new(),
order: Order::from_rooms_sort_order(config.rooms_sort_order), order: Order::from_rooms_sort_order(config.rooms_sort_order),
bell: BellState::new(),
euph_servers: HashMap::new(), euph_servers: HashMap::new(),
euph_rooms: HashMap::new(), euph_rooms: HashMap::new(),
}; };
@ -244,7 +246,9 @@ impl Rooms {
.retain(|n, r| !r.stopped() || rooms_set.contains(n)); .retain(|n, r| !r.stopped() || rooms_set.contains(n));
for room in rooms_set { for room in rooms_set {
self.get_or_insert_room(room).await.retain(); let room = self.get_or_insert_room(room).await;
room.retain();
self.bell.ring |= room.retrieve_mentioned();
} }
} }
@ -254,7 +258,7 @@ impl Rooms {
_ => self.stabilize_rooms().await, _ => self.stabilize_rooms().await,
} }
match &mut self.state { let widget = match &mut self.state {
State::ShowList => Self::rooms_widget( State::ShowList => Self::rooms_widget(
&self.vault, &self.vault,
self.config, self.config,
@ -297,6 +301,12 @@ impl Rooms {
.below(delete.widget()) .below(delete.widget())
.desync() .desync()
.boxed_async(), .boxed_async(),
};
if self.config.bell_on_mention {
widget.above(self.bell.widget().desync()).boxed_async()
} else {
widget
} }
} }