diff --git a/CHANGELOG.md b/CHANGELOG.md index 0728bc376..64b126be1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Changed: - `hidden_server_messages` has been changed to `server_messages` and additional customization has been added: - Exclude messages [join, part, quit]. - Adjust username format. + - Exclude server messages for users who have not messaged in the last X seconds Fixed: diff --git a/config.yaml b/config.yaml index 7f0b02e61..3369f8618 100644 --- a/config.yaml +++ b/config.yaml @@ -87,16 +87,18 @@ buffer: input_visibility: Always # Control different server messages. - # - exclude [boolean]: exclude the message from showing + # - exclude [All, None, !Smart seconds]: + # - Smart will show a server message if the user has sent a message + # in the given time interval (seconds) prior to the server message # - user_format [Short, Full]: controls the username formatting server_messages: join: - exclude: true + exclude: All username_format: Short part: - exclude: false + exclude: None quit: - exclude: false + exclude: !Smart 1200 # Channel buffer settings channel: diff --git a/data/src/config/buffer.rs b/data/src/config/buffer.rs index 9a2cb6303..67a0789fe 100644 --- a/data/src/config/buffer.rs +++ b/data/src/config/buffer.rs @@ -21,6 +21,14 @@ pub struct Buffer { pub server_messages: ServerMessages, } +#[derive(Debug, Copy, Clone, Default, Deserialize)] +pub enum Exclude { + #[default] + All, + None, + Smart(i64), +} + #[derive(Debug, Clone, Default, Deserialize)] pub struct ServerMessages { #[serde(default)] @@ -33,10 +41,10 @@ pub struct ServerMessages { impl ServerMessages { pub fn get(&self, server: &source::Server) -> ServerMessage { - match server { - source::Server::Join => self.join, - source::Server::Part => self.part, - source::Server::Quit => self.quit, + match server.kind() { + source::server::Kind::Join => self.join, + source::server::Kind::Part => self.part, + source::server::Kind::Quit => self.quit, } } } @@ -44,7 +52,7 @@ impl ServerMessages { #[derive(Debug, Copy, Clone, Default, Deserialize)] pub struct ServerMessage { #[serde(default)] - pub exclude: bool, + pub exclude: Exclude, #[serde(default)] pub username_format: UsernameFormat, } diff --git a/data/src/history/manager.rs b/data/src/history/manager.rs index cbca0b51c..a6f97e4f9 100644 --- a/data/src/history/manager.rs +++ b/data/src/history/manager.rs @@ -1,11 +1,12 @@ use std::collections::{HashMap, HashSet}; +use chrono::{DateTime, Utc}; use futures::future::BoxFuture; use futures::{future, Future, FutureExt}; use itertools::Itertools; use tokio::time::Instant; -use crate::config::buffer::ServerMessages; +use crate::config::buffer::{Exclude, ServerMessages}; use crate::history::{self, History}; use crate::message::{self, Limit}; use crate::time::Posix; @@ -437,14 +438,47 @@ impl Data { return None; }; + let mut most_recent_messages = HashMap::>::new(); + let filtered = messages .iter() - .filter(|message| { - if let message::Source::Server(Some(source)) = message.target.source() { - !server_messages.get(source).exclude - } else { + .filter(|message| match message.target.source() { + message::Source::Server(Some(source)) => { + let source_config = server_messages.get(source); + + match source_config.exclude { + Exclude::All => false, + Exclude::None => true, + Exclude::Smart(seconds) => { + if let Some(nick) = source.nick() { + !smart_filter_message( + message, + &seconds, + most_recent_messages.get(nick), + ) + } else if let Some(nickname) = + message.text.split(' ').collect::>().get(1) + { + let nick = Nick::from(*nickname); + + !smart_filter_message( + message, + &seconds, + most_recent_messages.get(&nick), + ) + } else { + true + } + } + } + } + crate::message::Source::User(message_user) => { + most_recent_messages + .insert(message_user.nickname().to_owned(), message.server_time); + true } + _ => true, }) .collect::>(); @@ -507,6 +541,23 @@ impl Data { } } +fn smart_filter_message( + message: &crate::Message, + seconds: &i64, + most_recent_message_server_time: Option<&DateTime>, +) -> bool { + let Some(server_time) = most_recent_message_server_time else { + return true; + }; + + let duration_seconds = message + .server_time + .signed_duration_since(*server_time) + .num_seconds(); + + duration_seconds > *seconds +} + #[derive(Debug, Clone)] pub enum Broadcast { Connecting, diff --git a/data/src/message.rs b/data/src/message.rs index 0344c759d..7bfcd3362 100644 --- a/data/src/message.rs +++ b/data/src/message.rs @@ -127,11 +127,17 @@ fn target(message: Encoded, our_nick: &Nick) -> Option { }), Command::PART(channel, _) => Some(Target::Channel { channel, - source: source::Source::Server(Some(source::Server::Part)), + source: source::Source::Server(Some(source::Server::new( + source::server::Kind::Part, + Some(user?.nickname().to_owned()), + ))), }), Command::JOIN(channel, _) => Some(Target::Channel { channel, - source: source::Source::Server(Some(source::Server::Join)), + source: source::Source::Server(Some(source::Server::new( + source::server::Kind::Join, + Some(user?.nickname().to_owned()), + ))), }), Command::Numeric(RPL_TOPIC | RPL_TOPICWHOTIME | RPL_CHANNELMODEIS, params) => { let channel = params.get(1)?.clone(); diff --git a/data/src/message/broadcast.rs b/data/src/message/broadcast.rs index 8efe648ba..058bb9338 100644 --- a/data/src/message/broadcast.rs +++ b/data/src/message/broadcast.rs @@ -129,7 +129,10 @@ pub fn quit( channels, queries, false, - Cause::Server(Some(source::Server::Quit)), + Cause::Server(Some(source::Server::new( + source::server::Kind::Quit, + Some(user.nickname().to_owned()), + ))), text, ) } diff --git a/data/src/message/source.rs b/data/src/message/source.rs index aa861b79f..e32371fc7 100644 --- a/data/src/message/source.rs +++ b/data/src/message/source.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::User; +pub use self::server::Server; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Source { User(User), @@ -10,14 +12,6 @@ pub enum Source { Internal(Internal), } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum Server { - Join, - Part, - Quit, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Internal { Status(Status), @@ -28,3 +22,52 @@ pub enum Status { Success, Error, } + +pub mod server { + #![allow(deprecated)] + use serde::{Deserialize, Serialize}; + + use crate::user::Nick; + + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] + #[serde(untagged)] + pub enum Server { + #[deprecated(note = "use Server::Details")] + Kind(Kind), + Details(Details), + } + + impl Server { + pub fn new(kind: Kind, nick: Option) -> Self { + Self::Details(Details { kind, nick }) + } + + pub fn kind(&self) -> Kind { + match self { + Server::Kind(kind) => *kind, + Server::Details(details) => details.kind, + } + } + + pub fn nick(&self) -> Option<&Nick> { + match self { + Server::Kind(_) => None, + Server::Details(details) => details.nick.as_ref(), + } + } + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] + #[serde(rename_all = "lowercase")] + pub enum Kind { + Join, + Part, + Quit, + } + + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] + pub struct Details { + pub kind: Kind, + pub nick: Option, + } +}