diff options
| -rw-r--r-- | CHANGELOG | 7 | ||||
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | documentation/mumctl.1 | 37 | ||||
| -rw-r--r-- | documentation/mumctl.txt | 31 | ||||
| -rw-r--r-- | mumctl/src/main.rs | 49 | ||||
| -rw-r--r-- | mumd/Cargo.toml | 1 | ||||
| -rw-r--r-- | mumd/src/network/tcp.rs | 41 | ||||
| -rw-r--r-- | mumd/src/state.rs | 205 | ||||
| -rw-r--r-- | mumd/src/state/server.rs | 9 | ||||
| -rw-r--r-- | mumlib/Cargo.toml | 2 | ||||
| -rw-r--r-- | mumlib/src/command.rs | 94 | ||||
| -rw-r--r-- | mumlib/src/error.rs | 4 |
12 files changed, 349 insertions, 133 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+. * Invalid server certificates are now rejected by default and need to be explicitly allowed either when connecting or permanently per server or globally. @@ -29,7 +30,8 @@ Added Changed ~~~~~~~ -* Changed how you mute yourself/others. See man pages for details on the new options. + * Changed how you mute yourself/others. See man pages for details on the new options. + * The current channel status is now printed when connecting to a server. // Removed // ~~~~~~~ @@ -42,6 +44,7 @@ Fixed be found. * Lots of other minor informative error messages instead of panics. * Status requests are sent in parallel. + * Pings are now less spammy in the log output. Other ~~~~~ @@ -139,6 +139,7 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] @@ -814,6 +815,7 @@ version = "0.4.0" dependencies = [ "bincode", "bytes", + "chrono", "cpal", "dasp_frame", "dasp_interpolate", diff --git a/documentation/mumctl.1 b/documentation/mumctl.1 index e875fd7..8d8ba72 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,11 +94,31 @@ 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. .RE .sp +mumctl message channel [\-r|\-\-recursive] <message> [<channel>...] +Sends a message to all channels specified in the list of channels. +If the recursive flag is set, the message is also sent to all subchannels in a recursive manner. +If no channels are given the message is sent to the channel currently +connected to. +.sp +mumctl message user <message> <users> +Sends a message to all users specified in the list of users. +.sp +mumctl messages [\-f|\-\-follow] +Prints all received messages since mumd was started, or since this command last was issued, +whichever happens first. +If the follow flag is set, mumctl will instead wait for new messages to come in and print +them as they come in. To exit this loop, issue a Ctrl\-C. +.sp mumctl mute [user] .RS 4 Mute yourself or someone else. @@ -157,19 +177,6 @@ mumctl volume <user> set <volume> Set the volume of another user\(cqs incoming audio. 1.0 is the default. .RE -.sp -mumctl messages [\-f|\-\-follow] -Prints all received messages since mumd was started, or since this command last was issued, -whichever happens first. -If the follow flag is set, mumctl will instead wait for new messages to come in and print -them as they come in. To exit this loop, issue a Ctrl\-C. -.sp -mumctl message user <message> <users> -Sends a message to all users specified in the list of users. -.sp -mumctl message channel [\-r|\-\-recursive] <message> <channels> -Sends a message to all channels specified in the list of channels. -If the recursive flag is set, the message is also sent to all subchannels in a recursive manner. .SH "AUTHORS" .sp Gustav Sörnäs and Eskil Queseth. diff --git a/documentation/mumctl.txt b/documentation/mumctl.txt index e513255..1c8ee8f 100644 --- a/documentation/mumctl.txt +++ b/documentation/mumctl.txt @@ -58,9 +58,27 @@ 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. +mumctl message channel [-r|--recursive] <message> [<channel>...] + Sends a message to all channels specified in the list of channels. + If the recursive flag is set, the message is also sent to all subchannels in a recursive manner. + If no channels are given the message is sent to the channel currently + connected to. + +mumctl message user <message> <users> + Sends a message to all users specified in the list of users. + +mumctl messages [-f|--follow] + Prints all received messages since mumd was started, or since this command last was issued, + whichever happens first. + If the follow flag is set, mumctl will instead wait for new messages to come in and print + them as they come in. To exit this loop, issue a Ctrl-C. + mumctl mute [user] :: Mute yourself or someone else. If user is omitted, you mute yourself. Otherwise, the user with the username [user] is muted. @@ -98,19 +116,6 @@ mumctl volume <user> set <volume> :: Set the volume of another user's incoming audio. 1.0 is the default. -mumctl messages [-f|--follow] - Prints all received messages since mumd was started, or since this command last was issued, - whichever happens first. - If the follow flag is set, mumctl will instead wait for new messages to come in and print - them as they come in. To exit this loop, issue a Ctrl-C. - -mumctl message user <message> <users> - Sends a message to all users specified in the list of users. - -mumctl message channel [-r|--recursive] <message> <channels> - Sends a message to all channels specified in the list of channels. - If the recursive flag is set, the message is also sent to all subchannels in a recursive manner. - Authors ------- diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs index bf1ffdc..5b2bc05 100644 --- a/mumctl/src/main.rs +++ b/mumctl/src/main.rs @@ -1,6 +1,6 @@ use colored::Colorize; use log::*; -use mumlib::command::{Command as MumCommand, CommandResponse, MessageTarget}; +use mumlib::command::{ChannelTarget, Command as MumCommand, CommandResponse, MessageTarget}; use mumlib::config::{self, Config, ServerConfig}; use mumlib::state::Channel as MumChannel; use serde::de::DeserializeOwned; @@ -89,6 +89,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)] @@ -96,10 +101,10 @@ enum Target { Channel { /// The message to send message: String, - /// If the message should be sent recursivley to sub-channels + /// If the message should be sent recursively to sub-channels #[structopt(short = "r", long = "recursive")] recursive: bool, - /// Which channels to send to + /// Which channels to send to. Defaults to current channel if left empty names: Vec<String>, }, User { @@ -274,10 +279,10 @@ fn match_opt() -> Result<(), Error> { accept_invalid_cert: cli_accept_invalid_cert || config_accept_invalid_cert.unwrap_or(false), })?; match response { - Ok(Some(CommandResponse::ServerConnect { welcome_message })) => { - println!("Connected to {}", host); + Ok(Some(CommandResponse::ServerConnect { welcome_message , server_state })) => { + parse_state(&server_state); if let Some(message) = welcome_message { - println!("Welcome: {}", message); + println!("\nWelcome: {}", message); } } Err(mumlib::error::Error::ServerCertReject) => { @@ -394,7 +399,7 @@ fn match_opt() -> Result<(), Error> { for response in send_command_multi(MumCommand::PastMessages { block: follow })? { match response { Ok(Some(CommandResponse::PastMessage { message })) => { - println!("{}: {}", message.1, message.0) + println!("[{}] {}: {}", message.0.format("%d %b %H:%M"), message.2, message.1) } Ok(_) => unreachable!("Response should only be a Some(PastMessages)"), Err(e) => error!("{}", e), @@ -409,24 +414,38 @@ fn match_opt() -> Result<(), Error> { } => { let msg = MumCommand::SendMessage { message, - targets: names - .into_iter() - .map(|name| MessageTarget::Channel { name, recursive }) - .collect(), + targets: if names.is_empty() { + MessageTarget::Channel(vec![(ChannelTarget::Current, recursive)]) + } else { + MessageTarget::Channel( + names + .into_iter() + .map(|name| (ChannelTarget::Named(name), recursive)) + .collect() + ) + }, }; send_command(msg)??; } Target::User { message, names } => { let msg = MumCommand::SendMessage { message, - targets: names - .into_iter() - .map(|name| MessageTarget::User { name }) - .collect(), + targets: MessageTarget::User(names), }; 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/Cargo.toml b/mumd/Cargo.toml index d8e2635..1e8e63f 100644 --- a/mumd/Cargo.toml +++ b/mumd/Cargo.toml @@ -42,6 +42,7 @@ tokio-stream = "0.1.0" tokio-native-tls = "0.3" tokio-util = { version = "0.6", features = ["codec", "net"] } bincode = "1.3.2" +chrono = "0.4" libnotify = { version = "1.0", optional = true } diff --git a/mumd/src/network/tcp.rs b/mumd/src/network/tcp.rs index cac1533..f620a32 100644 --- a/mumd/src/network/tcp.rs +++ b/mumd/src/network/tcp.rs @@ -11,6 +11,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; @@ -339,6 +340,10 @@ async fn listen( let mut crypt_state = None; let mut crypt_state_sender = Some(crypt_state_sender); + let mut last_late = 0; + let mut last_lost = 0; + let mut last_resync = 0; + loop { let packet = match stream.next().await { Some(Ok(packet)) => packet, @@ -367,6 +372,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); @@ -465,6 +472,40 @@ async fn listen( } } } + ControlPacket::Ping(msg) => { + trace!("Received Ping {:?}", *msg); + + let late = msg.get_late(); + let lost = msg.get_lost(); + let resync = msg.get_resync(); + + let late = late - last_late; + let lost = lost - last_lost; + let resync = resync - last_resync; + + last_late += late; + last_lost += lost; + last_resync += resync; + + macro_rules! format_if_nonzero { + ($value:expr) => { + if $value != 0 { + format!("\n {}: {}", stringify!($value), $value) + } else { + String::new() + } + } + } + + if late != 0 || lost != 0 || resync != 0 { + debug!( + "Ping:{}{}{}", + format_if_nonzero!(late), + format_if_nonzero!(lost), + format_if_nonzero!(resync), + ); + } + } packet => { debug!("Received unhandled ControlPacket {:#?}", packet); } diff --git a/mumd/src/state.rs b/mumd/src/state.rs index d93181a..d2d77b1 100644 --- a/mumd/src/state.rs +++ b/mumd/src/state.rs @@ -8,14 +8,15 @@ use crate::network::tcp::{TcpEvent, TcpEventData}; use crate::network::{ConnectionInfo, VoiceStreamType}; use crate::notifications; use crate::state::server::Server; - use crate::state::user::UserDiff; + +use chrono::NaiveDateTime; use log::*; 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::{ChannelTarget, Command, CommandResponse, MessageTarget, MumbleEvent, MumbleEventKind}; use mumlib::config::Config; use mumlib::Error; use std::{ @@ -74,9 +75,11 @@ pub struct State { server: Option<Server>, audio_input: AudioInput, audio_output: AudioOutput, - message_buffer: Vec<(String, u32)>, + message_buffer: Vec<(NaiveDateTime, String, u32)>, phase_watcher: (watch::Sender<StatePhase>, watch::Receiver<StatePhase>), + + events: Vec<MumbleEvent>, } impl State { @@ -97,6 +100,7 @@ impl State { audio_output, message_buffer: Vec::new(), phase_watcher, + events: Vec::new(), }; state.reload_config(); Ok(state) @@ -131,21 +135,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); } } } @@ -167,6 +175,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()) @@ -182,35 +191,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" }; } @@ -221,7 +238,8 @@ impl State { s += if deaf { " deafened" } else { " undeafened" }; } s += " themselves"; - notifications::send(s); + notifications::send(s.clone()); + self.push_event(MumbleEventKind::UserMuteStateChanged(s)); } } } @@ -235,11 +253,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() @@ -268,7 +300,7 @@ impl State { } pub fn register_message(&mut self, msg: (String, u32)) { - self.message_buffer.push(msg); + self.message_buffer.push((chrono::Local::now().naive_local(), msg.0, msg.1)); } pub fn broadcast_phase(&self, phase: StatePhase) { @@ -281,6 +313,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 } @@ -420,6 +457,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)) @@ -557,8 +607,9 @@ pub fn handle_command( accept_invalid_cert, ))) .unwrap(); + let state = Arc::clone(&og_state); at!( - TcpEvent::Connected => |res| { + TcpEvent::Connected => move |res| { //runs the closure when the client is connected if let TcpEventData::Connected(res) = res { Box::new(iter::once(res.map(|msg| { @@ -568,6 +619,7 @@ pub fn handle_command( } else { None }, + server_state: state.read().unwrap().server.as_ref().unwrap().into(), }) }))) } else { @@ -608,12 +660,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, - }) + } })) }), ), @@ -657,6 +709,7 @@ pub fn handle_command( Box::new(move |data, sender| { if let TcpEventData::TextMessage(a) = data { let message = ( + chrono::Local::now().naive_local(), a.get_message().to_owned(), ref_state .read() @@ -676,7 +729,7 @@ pub fn handle_command( let messages = std::mem::take(&mut state.message_buffer); let messages: Vec<_> = messages .into_iter() - .map(|(msg, user)| (msg, state.get_user_name(user).unwrap())) + .map(|(timestamp, msg, user)| (timestamp, msg, state.get_user_name(user).unwrap())) .map(|e| Ok(Some(CommandResponse::PastMessage { message: e }))) .collect(); @@ -692,43 +745,45 @@ pub fn handle_command( msg.set_message(message); - for target in targets { - match target { - MessageTarget::Channel { recursive, name } => { - let channel_id = state.server().unwrap().channel_name(&name); - - let channel_id = match channel_id { - Ok(id) => id, + match targets { + MessageTarget::Channel(channels) => for (channel, recursive) in channels { + let channel_id = if let ChannelTarget::Named(name) = channel { + let channel = state.server().unwrap().channel_name(&name); + match channel { + Ok(channel) => channel.0, Err(e) => return now!(Err(Error::ChannelIdentifierError(name, e))), } - .0; - - if recursive { - msg.mut_tree_id() - } else { - msg.mut_channel_id() + } else { + match state.server().unwrap().current_channel() { + Some(channel) => channel.0, + None => return now!(Err(Error::NotConnectedToChannel)), } - .push(channel_id); - } - MessageTarget::User { name } => { - let id = state - .server() - .unwrap() - .users() - .iter() - .find(|(_, user)| user.name() == &name) - .map(|(e, _)| *e); - - let id = match id { - Some(id) => id, - None => return now!(Err(Error::InvalidUsername(name))), - }; - - msg.mut_session().push(id); - } + }; + + let ids = if recursive { + msg.mut_tree_id() + } else { + msg.mut_channel_id() + }; + ids.push(channel_id); + } + MessageTarget::User(names) => for name in names { + let id = state + .server() + .unwrap() + .users() + .iter() + .find(|(_, user)| user.name() == &name) + .map(|(e, _)| *e); + + let id = match id { + Some(id) => id, + None => return now!(Err(Error::InvalidUsername(name))), + }; + + msg.mut_session().push(id); } } - packet_sender.send(msg.into()).unwrap(); now!(Ok(None)) diff --git a/mumd/src/state/server.rs b/mumd/src/state/server.rs index 869940a..4abde49 100644 --- a/mumd/src/state/server.rs +++ b/mumd/src/state/server.rs @@ -132,6 +132,15 @@ impl Server { }) } + /// Returns the currenctly connected channel. + /// + /// Returns None if not connected. + pub fn current_channel(&self) -> Option<(u32, &Channel)> { + let channel_id = self.users().get(&self.session_id()?)?.channel(); + let channel = self.channels().get(&channel_id)?; + Some((channel_id, channel)) + } + pub fn host_mut(&mut self) -> &mut Option<String> { &mut self.host } diff --git a/mumlib/Cargo.toml b/mumlib/Cargo.toml index 5c9d4e1..5ec9365 100644 --- a/mumlib/Cargo.toml +++ b/mumlib/Cargo.toml @@ -13,7 +13,7 @@ readme = "../README.md" [dependencies] colored = "2" -chrono = "0.4" +chrono = { version = "0.4", features = [ "serde" ] } dirs = "3" fern = "0.6" log = "0.4" diff --git a/mumlib/src/command.rs b/mumlib/src/command.rs index 351d7f6..8cb7cb9 100644 --- a/mumlib/src/command.rs +++ b/mumlib/src/command.rs @@ -1,6 +1,60 @@ 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 { @@ -10,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: MessageTarget, + }, ServerConnect { host: String, port: u16, @@ -29,13 +93,6 @@ pub enum Command { }, Status, UserVolumeSet(String, f32), - PastMessages { - block: bool, - }, - SendMessage { - message: String, - targets: Vec<MessageTarget>, - }, } #[derive(Debug, Deserialize, Serialize)] @@ -46,12 +103,19 @@ 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>, + server_state: Server, }, ServerStatus { version: u32, @@ -62,13 +126,19 @@ pub enum CommandResponse { Status { server_state: Server, }, - PastMessage { - message: (String, String), - }, } +/// Messages sent to channels can be sent either to a named channel or the +/// currently connected channel. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum ChannelTarget { + Current, + Named(String) +} + +/// Messages can be sent to either channels or specific users. #[derive(Clone, Debug, Deserialize, Serialize)] pub enum MessageTarget { - Channel { recursive: bool, name: String }, - User { name: String }, + Channel(Vec<(ChannelTarget, bool)>), // (target, recursive) + User(Vec<String>), } diff --git a/mumlib/src/error.rs b/mumlib/src/error.rs index 91c15e1..30b61ee 100644 --- a/mumlib/src/error.rs +++ b/mumlib/src/error.rs @@ -11,6 +11,8 @@ pub enum Error { InvalidServerAddr(String, u16), InvalidUsername(String), InvalidServerPassword, + Unimplemented, + NotConnectedToChannel, ServerCertReject, } @@ -27,6 +29,8 @@ 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"), + Error::NotConnectedToChannel => write!(f, "Not connected to a channel"), Error::ServerCertReject => write!(f, "Invalid server certificate"), } } |
