From ae411b6dcc24e5a92bc3a684c52c7941f6d04d64 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger <tobias@msrg.cc> Date: Wed, 10 Apr 2024 17:36:51 +0200 Subject: [PATCH 1/2] Improve events Add known clients (cache) for leaving clients Add message to returned json object --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/command_utils/events.rs | 14 +++-- src/models/client.rs | 2 +- src/models/event.rs | 109 ++++++++++++++++++++++++++++++++---- src/parameter.rs | 2 +- ts-control | 10 ++-- 7 files changed, 117 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e5639b..c66e204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ [[package]] name = "teamspeak-query-lib" -version = "0.1.2" +version = "0.1.4" dependencies = [ "clap", "serde", diff --git a/Cargo.toml b/Cargo.toml index 8930810..abc17c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "teamspeak-query-lib" -version = "0.1.2" +version = "0.1.4" edition = "2021" [dependencies] diff --git a/src/command_utils/events.rs b/src/command_utils/events.rs index b4a8773..00ccaca 100644 --- a/src/command_utils/events.rs +++ b/src/command_utils/events.rs @@ -1,6 +1,6 @@ use telnet::Telnet; -use crate::commands; -use crate::models::{Event, EventType}; +use crate::{commands, wrappers}; +use crate::models::{Client, Event, EventType}; use crate::response::Response; pub fn register_events(connection: &mut Telnet, events: Vec<EventType>) -> Result<(), String> { @@ -12,10 +12,10 @@ pub fn register_events(connection: &mut Telnet, events: Vec<EventType>) -> Resul Ok(()) } -pub fn handle_event_response(connection: &mut Telnet, response: Response) { +pub fn handle_event_response(connection: &mut Telnet, response: Response, known_clients: &mut Vec<Client>) { if let Response::Event(event_type, params) = response { - let event = Event::new(connection, event_type, params); + let event = Event::new(connection, event_type, params, &known_clients); match serde_json::to_string(&event) { Ok(json) => println!("{}", json), Err(err) => { @@ -24,13 +24,17 @@ pub fn handle_event_response(connection: &mut Telnet, response: Response) { } } + if let Ok(new_clients) = wrappers::get_clients(connection) { + *known_clients = new_clients; + } } } pub fn loop_response_reader(connection: &mut Telnet) { + let mut known_clients = wrappers::get_clients(connection).unwrap_or_else(|_| Vec::new()); loop { match commands::read_response(connection, true, String::new()) { - Ok(response) => handle_event_response(connection, response), + Ok(response) => handle_event_response(connection, response, &mut known_clients), Err(_) => { // print error? return; diff --git a/src/models/client.rs b/src/models/client.rs index fe38e06..41351e9 100644 --- a/src/models/client.rs +++ b/src/models/client.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::parameter::{parameter_find, ParameterList}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct Client { pub cid: i32, pub clid: i32, diff --git a/src/models/event.rs b/src/models/event.rs index 1d383c8..50360bd 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -1,7 +1,8 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; +use serde::ser::SerializeStruct; use telnet::Telnet; use crate::models::{Channel, Client}; @@ -43,7 +44,7 @@ pub enum EventType { NotifyConnectStatusChange, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Debug)] pub struct Event { pub event_type: EventType, pub params: ParameterList, @@ -152,36 +153,58 @@ impl Display for EventType { } impl EventType { + + pub fn find_channel(&self, connection: &mut Telnet, params: &ParameterList, param_name: &str) -> Option<Channel> { + let id = parameter_find(params, param_name)? + .parse::<i32>().unwrap_or(0); + wrappers::find_channel(connection, "cid", &id.to_string(), true).unwrap().or(None) + } pub fn get_channel(&self, connection: &mut Telnet, params: &ParameterList) -> Option<Channel> { match self { EventType::NotifyClientMoved => { - let id = parameter_find(params, "ctid")? - .parse::<i32>().unwrap_or(0); - wrappers::find_channel(connection, "cid", &id.to_string(), true).unwrap().or(None) + self.find_channel(connection, params, "ctid") } _ => None, } } + pub fn find_client(&self, connection: &mut Telnet, params: &ParameterList, param_name: &str) -> Option<Client> { + let id = parameter_find(params, param_name)? + .parse::<i32>().unwrap_or(0); + wrappers::find_client(connection, "clid", &id.to_string(), true).unwrap().or(None) + } + pub fn get_client(&self, connection: &mut Telnet, params: &ParameterList) -> Option<Client> { match self { EventType::NotifyClientMoved => { - let id = parameter_find(params, "clid")? - .parse::<i32>().unwrap_or(0); - wrappers::find_client(connection, "clid", &id.to_string(), true).unwrap().or(None) + self.find_client(connection, params, "clid") } EventType::NotifyClientEnterView => { Some(Client::from(params.clone())) } + EventType::NotifyTextMessage => { + self.find_client(connection, params, "invokerid") + } + EventType::NotifyClientPoke => { + self.find_client(connection, params, "invokerid") + } _ => None, } } } impl Event { - pub fn new(connection: &mut Telnet, event_type: EventType, params: ParameterList) -> Event { + pub fn new(connection: &mut Telnet, event_type: EventType, params: ParameterList, known_clients: &[Client]) -> Event { let channel = event_type.get_channel(connection, ¶ms); - let client = event_type.get_client(connection, ¶ms); + let mut client = event_type.get_client(connection, ¶ms); + + if client.is_none() { + let id = parameter_find(¶ms, "clid") + .unwrap_or_else(|| String::from("0")) + .parse::<i32>().unwrap_or(0); + let client_id = id.to_string(); + client = known_clients.iter().find(|c| c.clid.to_string() == client_id).cloned(); + } Event { event_type, @@ -190,4 +213,70 @@ impl Event { client, } } + + pub fn get_client_name(&self) -> String { + match &self.client { + Some(client) => client.client_nickname.clone(), + None => String::from("UNKNOWN"), + } + } + + pub fn get_channel_name(&self) -> String { + match &self.channel { + Some(channel) => channel.channel_name.clone(), + None => String::from("UNKNOWN"), + } + } + + pub fn get_event_msg(&self) -> String { + parameter_find(&self.params, "msg").unwrap_or_else(|| String::from("")) + } + + pub fn get_message(&self) -> String { + match &self.event_type { + EventType::NotifyClientPoke => { + format!("{}: {}", + self.get_client_name(), + self.get_event_msg() + ) + } + EventType::NotifyTextMessage => { + format!("{}: {}", + self.get_client_name(), + self.get_event_msg() + ) + } + EventType::NotifyClientMoved => { + format!("{} joined {}", + self.get_client_name(), + self.get_channel_name() + ) + } + EventType::NotifyClientLeftView => { + format!("{} left", + self.get_client_name() + ) + } + EventType::NotifyClientEnterView => { + format!("{} connected", + self.get_client_name() + ) + } + _ => String::from(""), + } + } +} + +impl Serialize for Event { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut x = serializer.serialize_struct("Event", 4)?; + x.serialize_field("type", &self.event_type)?; + x.serialize_field("channel", &self.channel)?; + x.serialize_field("client", &self.client)?; + x.serialize_field("message", &self.get_message())?; + x.end() + } } \ No newline at end of file diff --git a/src/parameter.rs b/src/parameter.rs index 6424ecb..206412b 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -44,7 +44,7 @@ pub fn parameter_parse(params_str: &str) -> ParameterList { let mut response_params = ParameterList::new(); parts.iter().for_each(|part| { - let (key, value) = part.split_once('=').unwrap_or((part, "1")); + let (key, value) = part.split_once('=').unwrap_or((part, "")); response_params.insert(key.to_string(), decode_value(value)); }); diff --git a/ts-control b/ts-control index 462cb7c..31adc4e 100755 --- a/ts-control +++ b/ts-control @@ -9,7 +9,7 @@ not away back message message-user -events-move" +events" _ts_control_get_entity() { entity=$(_ts_control_single_or_dmenu "$(teamspeak-query-lib "$1s")" "$2") @@ -98,8 +98,8 @@ case $action in message=$(_ts_control_get_message "$3") teamspeak-query-lib message --strict-client --client "$user" "$message" ;; - "events-move") - teamspeak-query-lib events NotifyClientMoved NotifyClientEnterView \ - | jq -r --unbuffered '.client.client_nickname + " joined " + .channel.channel_name // "the server"' \ - | tee >(xargs -I{} notify-send "TS3 movement" "{}") + "events") + teamspeak-query-lib events NotifyClientMoved NotifyClientEnterView NotifyClientLeftView NotifyTextMessage NotifyClientPoke \ + | jq -r --unbuffered '.message' \ + | tee >(xargs -I{} notify-send "TS3 Event" "{}") esac From ea7815535c8e437852bbf1cdba88d61534b1be03 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger <tobias@msrg.cc> Date: Wed, 10 Apr 2024 17:40:51 +0200 Subject: [PATCH 2/2] Fix clippy issues --- src/command_utils/events.rs | 2 +- src/parameter.rs | 8 ++++---- src/response.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/command_utils/events.rs b/src/command_utils/events.rs index 00ccaca..8e3e585 100644 --- a/src/command_utils/events.rs +++ b/src/command_utils/events.rs @@ -15,7 +15,7 @@ pub fn register_events(connection: &mut Telnet, events: Vec<EventType>) -> Resul pub fn handle_event_response(connection: &mut Telnet, response: Response, known_clients: &mut Vec<Client>) { if let Response::Event(event_type, params) = response { - let event = Event::new(connection, event_type, params, &known_clients); + let event = Event::new(connection, event_type, params, known_clients); match serde_json::to_string(&event) { Ok(json) => println!("{}", json), Err(err) => { diff --git a/src/parameter.rs b/src/parameter.rs index 206412b..b2476a0 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -5,7 +5,7 @@ pub type ParameterList = HashMap<String, String>; pub type Parameter = (String, String); pub fn parameter_find(params: &ParameterList, name: &str) -> Option<String> { - params.get(name).map(|value| value.clone()) + params.get(name).cloned() } pub fn parameter_list_has(params: &ParameterList, key: &str, value: &str, strict: bool) -> bool { @@ -17,7 +17,7 @@ pub fn parameter_list_has(params: &ParameterList, key: &str, value: &str, strict return true; } } - return false; + false } pub fn parameter_list_find(param_lists: &Vec<ParameterList>, key: &str, value: &str, strict: bool) -> Option<ParameterList> { @@ -58,7 +58,7 @@ pub fn parameter_to_string(param: Parameter) -> String { pub fn parameter_list_to_string(parameter_list: ParameterList, sep: &str) -> String { parameter_list .into_iter() - .map(|param| parameter_to_string(param)) + .map(parameter_to_string) .collect::<Vec<String>>() .join(sep) } @@ -66,7 +66,7 @@ pub fn parameter_list_to_string(parameter_list: ParameterList, sep: &str) -> Str pub fn parameters_to_string(parameters: Vec<Parameter>, sep: &str) -> String { parameters .into_iter() - .map(|param| parameter_to_string(param)) + .map(parameter_to_string) .collect::<Vec<String>>() .join(sep) } \ No newline at end of file diff --git a/src/response.rs b/src/response.rs index aed60d2..7901fa5 100644 --- a/src/response.rs +++ b/src/response.rs @@ -45,7 +45,7 @@ fn get_response_type(type_str: &str) -> Result<ResponseType, String> { return Ok(ResponseType::Event(event_type)); } - return Ok(ResponseType::Unknown); + Ok(ResponseType::Unknown) } impl TryFrom<String> for Response { @@ -54,7 +54,7 @@ impl TryFrom<String> for Response { fn try_from(response_str: String) -> Result<Self, ResponseError> { let response_str = response_str.trim_end_matches("\n\r"); - let (rsp_type, rsp_params) = split_type_and_params(&response_str) + let (rsp_type, rsp_params) = split_type_and_params(response_str) .map_err(|err| ResponseError { id: -1, msg: err, @@ -66,7 +66,7 @@ impl TryFrom<String> for Response { parameter_lists.push(response_params); } - return match rsp_type { + match rsp_type { ResponseType::Error => { Err(ResponseError::create_error(¶meter_lists[0])) }