diff options
| -rw-r--r-- | CHANGELOG | 3 | ||||
| -rw-r--r-- | documentation/mumctl.1 | 9 | ||||
| -rw-r--r-- | documentation/mumctl.txt | 3 | ||||
| -rw-r--r-- | mumctl/src/main.rs | 16 | ||||
| -rw-r--r-- | mumd/src/network/tcp.rs | 3 | ||||
| -rw-r--r-- | mumd/src/state.rs | 125 | ||||
| -rw-r--r-- | mumlib/src/command.rs | 79 | ||||
| -rw-r--r-- | mumlib/src/error.rs | 2 |
8 files changed, 189 insertions, 51 deletions
@@ -21,7 +21,8 @@ Added * Added tunneling audio through TCP if UDP connection goes down. * --version now includes the current commit hash. * Server passwords. Thanks @rbran! - * Added support for sending and receiving text messages + * Added support for sending and receiving text messages. + * See a list of occured events with +mumctl events+. Changed ~~~~~~~ diff --git a/documentation/mumctl.1 b/documentation/mumctl.1 index e875fd7..c50398f 100644 --- a/documentation/mumctl.1 +++ b/documentation/mumctl.1 @@ -2,12 +2,12 @@ .\" Title: mumd .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.15 -.\" Date: 2021-06-06 +.\" Date: 2021-06-08 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "MUMCTL" "1" "2021-06-06" "\ \&" "\ \&" +.TH "MUMCTL" "1" "2021-06-08" "\ \&" "\ \&" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -94,6 +94,11 @@ mumctl disconnect Disconnect from the currently connected server. .RE .sp +mumctl events +.RS 4 +Print all events that have occured since mumd was started. +.RE +.sp mumctl help .RS 4 Show a help message. diff --git a/documentation/mumctl.txt b/documentation/mumctl.txt index e513255..68e4c0e 100644 --- a/documentation/mumctl.txt +++ b/documentation/mumctl.txt @@ -58,6 +58,9 @@ mumctl deafen :: mumctl disconnect :: Disconnect from the currently connected server. +mumctl events :: + Print all events that have occured since mumd was started. + mumctl help :: Show a help message. diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs index 8b0eeb2..f704b19 100644 --- a/mumctl/src/main.rs +++ b/mumctl/src/main.rs @@ -87,6 +87,11 @@ enum Command { }, /// Send a message to a channel or a user Message(Target), + /// Get events that have happened since we connected + Events { + #[structopt(short = "f", long = "follow")] + follow: bool, + }, } #[derive(Debug, StructOpt)] @@ -399,6 +404,17 @@ fn match_opt() -> Result<(), Error> { send_command(msg)??; } }, + Command::Events { follow } => { + for response in send_command_multi(MumCommand::Events { block: follow })? { + match response { + Ok(Some(CommandResponse::Event { event })) => { + println!("{}", event) + } + Ok(_) => unreachable!("Response should only be a Some(Event)"), + Err(e) => error!("{}", e), + } + } + } } let config_path = config::default_cfg_path(); diff --git a/mumd/src/network/tcp.rs b/mumd/src/network/tcp.rs index 5cc2bf7..4140f2a 100644 --- a/mumd/src/network/tcp.rs +++ b/mumd/src/network/tcp.rs @@ -13,6 +13,7 @@ use mumble_protocol::control::{msgs, ClientControlCodec, ControlCodec, ControlPa use mumble_protocol::crypt::ClientCryptState; use mumble_protocol::voice::VoicePacket; use mumble_protocol::{Clientbound, Serverbound}; +use mumlib::command::MumbleEventKind; use std::collections::HashMap; use std::convert::{Into, TryInto}; use std::net::SocketAddr; @@ -337,6 +338,8 @@ async fn listen( if let Some(user) = user { notifications::send(format!("{}: {}", user, msg.get_message())); //TODO: probably want a config flag for this + let user = user.to_string(); + state.push_event(MumbleEventKind::TextMessageReceived(user)) //TODO also include message target } state.register_message((msg.get_message().to_owned(), msg.get_actor())); drop(state); diff --git a/mumd/src/state.rs b/mumd/src/state.rs index 4530e46..2cfdfb1 100644 --- a/mumd/src/state.rs +++ b/mumd/src/state.rs @@ -16,7 +16,7 @@ use mumble_protocol::control::msgs; use mumble_protocol::control::ControlPacket; use mumble_protocol::ping::PongPacket; use mumble_protocol::voice::Serverbound; -use mumlib::command::{Command, CommandResponse, MessageTarget}; +use mumlib::command::{Command, CommandResponse, MessageTarget, MumbleEvent, MumbleEventKind}; use mumlib::config::Config; use mumlib::Error; use std::{ @@ -76,6 +76,8 @@ pub struct State { message_buffer: Vec<(NaiveDateTime, String, u32)>, phase_watcher: (watch::Sender<StatePhase>, watch::Receiver<StatePhase>), + + events: Vec<MumbleEvent>, } impl State { @@ -96,6 +98,7 @@ impl State { audio_output, message_buffer: Vec::new(), phase_watcher, + events: Vec::new(), }; state.reload_config(); Ok(state) @@ -130,21 +133,25 @@ impl State { // this is someone else // send notification only if we've passed the connecting phase if matches!(*self.phase_receiver().borrow(), StatePhase::Connected(_)) { - let channel_id = msg.get_channel_id(); - - if channel_id - == self.get_users_channel(self.server().unwrap().session_id().unwrap()) - { - if let Some(channel) = self.server().unwrap().channels().get(&channel_id) { - notifications::send(format!( - "{} connected and joined {}", - &msg.get_name(), - channel.name() - )); - } - - self.audio_output - .play_effect(NotificationEvents::UserConnected); + let this_channel = msg.get_channel_id(); + let other_channel = self.get_users_channel(self.server().unwrap().session_id().unwrap()); + let this_channel_name = self + .server() + .unwrap() + .channels() + .get(&this_channel) + .map(|c| c.name()) + .unwrap_or("<unnamed channel>") + .to_string(); + + if this_channel == other_channel { + notifications::send(format!( + "{} connected and joined {}", + msg.get_name(), + this_channel_name, + )); + self.push_event(MumbleEventKind::UserConnected(msg.get_name().to_string(), this_channel_name)); + self.audio_output.play_effect(NotificationEvents::UserConnected); } } } @@ -166,6 +173,7 @@ impl State { .users_mut() .get_mut(&session) .unwrap(); + let username = user.name().to_string(); let mute = if msg.has_self_mute() && user.self_mute() != msg.get_self_mute() { Some(msg.get_self_mute()) @@ -181,35 +189,43 @@ impl State { let diff = UserDiff::from(msg); user.apply_user_diff(&diff); - let user = self.server().unwrap().users().get(&session).unwrap(); if Some(session) != self.server().unwrap().session_id() { - //send notification if the user moved to or from any channel + // Send notification if the user moved either to or from our channel if let Some(to_channel) = diff.channel_id { let this_channel = self.get_users_channel(self.server().unwrap().session_id().unwrap()); - if from_channel == this_channel || to_channel == this_channel { + + if from_channel == this_channel { + // User moved from our channel to somewhere else if let Some(channel) = self.server().unwrap().channels().get(&to_channel) { + let channel = channel.name().to_string(); notifications::send(format!( "{} moved to channel {}", - user.name(), - channel.name() + &username, + &channel, )); - } else { - warn!("{} moved to invalid channel {}", user.name(), to_channel); + self.push_event(MumbleEventKind::UserLeftChannel(username.clone(), channel)); } - self.audio_output - .play_effect(if from_channel == this_channel { - NotificationEvents::UserJoinedChannel - } else { - NotificationEvents::UserLeftChannel - }); + self.audio_output.play_effect(NotificationEvents::UserLeftChannel); + } else if to_channel == this_channel { + // User moved from somewhere else to our channel + if let Some(channel) = self.server().unwrap().channels().get(&from_channel) { + let channel = channel.name().to_string(); + notifications::send(format!( + "{} moved to your channel from {}", + &username, + &channel, + )); + self.push_event(MumbleEventKind::UserJoinedChannel(username.clone(), channel)); + } + self.audio_output.play_effect(NotificationEvents::UserJoinedChannel); } } //send notification if a user muted/unmuted if mute != None || deaf != None { - let mut s = user.name().to_string(); + let mut s = username; if let Some(mute) = mute { s += if mute { " muted" } else { " unmuted" }; } @@ -220,7 +236,8 @@ impl State { s += if deaf { " deafened" } else { " undeafened" }; } s += " themselves"; - notifications::send(s); + notifications::send(s.clone()); + self.push_event(MumbleEventKind::UserMuteStateChanged(s)); } } } @@ -234,11 +251,25 @@ impl State { let this_channel = self.get_users_channel(self.server().unwrap().session_id().unwrap()); let other_channel = self.get_users_channel(msg.get_session()); if this_channel == other_channel { - self.audio_output - .play_effect(NotificationEvents::UserDisconnected); - if let Some(user) = self.server().unwrap().users().get(&msg.get_session()) { - notifications::send(format!("{} disconnected", &user.name())); - } + let channel_name = self + .server() + .unwrap() + .channels() + .get(&this_channel) + .map(|c| c.name()) + .unwrap_or("<unnamed channel>") + .to_string(); + let user_name = self + .server() + .unwrap() + .users() + .get(&msg.get_session()) + .map(|u| u.name()) + .unwrap_or("<unknown user>") + .to_string(); + notifications::send(format!("{} disconnected", &user_name)); + self.push_event(MumbleEventKind::UserDisconnected(user_name, channel_name)); + self.audio_output.play_effect(NotificationEvents::UserDisconnected); } self.server_mut() @@ -280,6 +311,11 @@ impl State { .play_effect(NotificationEvents::ServerConnect); } + /// Store a new event + pub fn push_event(&mut self, kind: MumbleEventKind) { + self.events.push(MumbleEvent { timestamp: chrono::Local::now().naive_local(), kind }); + } + pub fn audio_input(&self) -> &AudioInput { &self.audio_input } @@ -419,6 +455,19 @@ pub fn handle_command( new_deaf.map(|b| CommandResponse::DeafenStatus { is_deafened: b }) )) } + Command::Events { block } => { + if block { + warn!("Blocking event list is unimplemented"); + now!(Err(Error::Unimplemented)) + } else { + let events: Vec<_> = state + .events + .iter() + .map(|event| Ok(Some(CommandResponse::Event { event: event.clone() }))) + .collect(); + ExecutionContext::Now(Box::new(move || Box::new(events.into_iter()))) + } + } Command::InputVolumeSet(volume) => { state.audio_input.set_volume(volume); now!(Ok(None)) @@ -602,12 +651,12 @@ pub fn handle_command( }), Box::new(move |pong| { Ok(pong.map(|pong| { - (CommandResponse::ServerStatus { + CommandResponse::ServerStatus { version: pong.version, users: pong.users, max_users: pong.max_users, bandwidth: pong.bandwidth, - }) + } })) }), ), diff --git a/mumlib/src/command.rs b/mumlib/src/command.rs index 27ca60c..48174fc 100644 --- a/mumlib/src/command.rs +++ b/mumlib/src/command.rs @@ -2,6 +2,59 @@ use crate::state::{Channel, Server}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Something that happened in our channel at a point in time. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MumbleEvent { + pub timestamp: NaiveDateTime, + pub kind: MumbleEventKind +} + +impl fmt::Display for MumbleEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[{}] {}", self.timestamp.format("%d %b %H:%M"), self.kind) + } +} + +/// The different kinds of events that can happen. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum MumbleEventKind { + UserConnected(String, String), + UserDisconnected(String, String), + UserMuteStateChanged(String), // This logic is kinda weird so we only store the rendered message. + TextMessageReceived(String), + UserJoinedChannel(String, String), + UserLeftChannel(String, String), +} + +//TODO These strings are (mostly) duplicated with their respective notifications. +// The only difference is that the text message event doesn't contain the text message. +impl fmt::Display for MumbleEventKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MumbleEventKind::UserConnected(user, channel) => { + write!(f, "{} connected to {}", user, channel) + } + MumbleEventKind::UserDisconnected(user, channel) => { + write!(f, "{} disconnected from {}", user, channel) + } + MumbleEventKind::UserMuteStateChanged(message) => { + write!(f, "{}", message) + } + MumbleEventKind::TextMessageReceived(user) => { + write!(f, "{} sent a text message", user) + } + MumbleEventKind::UserJoinedChannel(name, from) => { + write!(f, "{} moved to your channel from {}", name, from) + } + MumbleEventKind::UserLeftChannel(name, to) => { + write!(f, "{} moved to {}", name, to) + } + + } + } +} #[derive(Clone, Debug, Deserialize, Serialize)] pub enum Command { @@ -11,11 +64,21 @@ pub enum Command { ChannelList, ConfigReload, DeafenSelf(Option<bool>), + Events { + block: bool + }, InputVolumeSet(f32), MuteOther(String, Option<bool>), MuteSelf(Option<bool>), OutputVolumeSet(f32), + PastMessages { + block: bool, + }, Ping, + SendMessage { + message: String, + targets: Vec<MessageTarget>, + }, ServerConnect { host: String, port: u16, @@ -30,13 +93,6 @@ pub enum Command { }, Status, UserVolumeSet(String, f32), - PastMessages { - block: bool, - }, - SendMessage { - message: String, - targets: Vec<MessageTarget>, - }, } #[derive(Debug, Deserialize, Serialize)] @@ -47,9 +103,15 @@ pub enum CommandResponse { DeafenStatus { is_deafened: bool, }, + Event { + event: MumbleEvent, + }, MuteStatus { is_muted: bool, }, + PastMessage { + message: (NaiveDateTime, String, String), + }, Pong, ServerConnect { welcome_message: Option<String>, @@ -63,9 +125,6 @@ pub enum CommandResponse { Status { server_state: Server, }, - PastMessage { - message: (NaiveDateTime, String, String), - }, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/mumlib/src/error.rs b/mumlib/src/error.rs index e88bd97..c492a5f 100644 --- a/mumlib/src/error.rs +++ b/mumlib/src/error.rs @@ -11,6 +11,7 @@ pub enum Error { InvalidServerAddr(String, u16), InvalidUsername(String), InvalidServerPassword, + Unimplemented, } impl std::error::Error for Error {} @@ -26,6 +27,7 @@ impl fmt::Display for Error { } Error::InvalidUsername(username) => write!(f, "Invalid username: {}", username), Error::InvalidServerPassword => write!(f, "Invalid server password"), + Error::Unimplemented => write!(f, "Unimplemented"), } } } |
