aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG7
-rw-r--r--Cargo.lock2
-rw-r--r--documentation/mumctl.137
-rw-r--r--documentation/mumctl.txt31
-rw-r--r--mumctl/src/main.rs49
-rw-r--r--mumd/Cargo.toml1
-rw-r--r--mumd/src/network/tcp.rs41
-rw-r--r--mumd/src/state.rs205
-rw-r--r--mumd/src/state/server.rs9
-rw-r--r--mumlib/Cargo.toml2
-rw-r--r--mumlib/src/command.rs94
-rw-r--r--mumlib/src/error.rs4
12 files changed, 349 insertions, 133 deletions
diff --git a/CHANGELOG b/CHANGELOG
index e4bc446..ef99d3d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
~~~~~
diff --git a/Cargo.lock b/Cargo.lock
index 679a1e9..2f4bb07 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"),
}
}