From d8cdc2bb1194814bba0c970ddee0f18a2d541716 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Tue, 5 Mar 2024 03:52:30 +0100 Subject: [PATCH] Add events and refactor Add event listener with JSON output (WIP) Add notifier on movement events Refactor Parameter and ParameterList (still shit) --- Cargo.lock | 45 +++++++++++ Cargo.toml | 3 + src/cli.rs | 34 ++++---- src/commands.rs | 17 ++-- src/main.rs | 35 +++++++- src/models/channel.rs | 17 ++-- src/models/client.rs | 14 ++-- src/models/event.rs | 182 +++++++++++++++++++++++++++++++++--------- src/models/mod.rs | 2 +- src/parameter.rs | 119 ++++++++------------------- src/response.rs | 29 ++++--- src/utils.rs | 10 +-- src/wrappers.rs | 10 +-- ts-control | 7 +- 14 files changed, 338 insertions(+), 186 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01db8ec..d658b69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -120,6 +126,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -142,6 +185,8 @@ name = "teamspeak-query-lib" version = "0.1.1" dependencies = [ "clap", + "serde", + "serde_json", "telnet", ] diff --git a/Cargo.toml b/Cargo.toml index d43dbcd..a39f88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [dependencies] telnet = "0.2" clap = { version = "4.4", features = ["derive"] } + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/cli.rs b/src/cli.rs index f518b41..9cfdb0d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,8 @@ use clap::{Args, Parser, Subcommand}; use telnet::Telnet; -use crate::parameter::{Parameter, ParameterList}; -use crate::models::Channel; +use crate::parameter::ParameterList; +use crate::models::{Channel, EventType}; use crate::models::Client; use crate::utils::SendTextMessageTarget; use crate::wrappers; @@ -23,6 +23,7 @@ pub enum Commands { Message(MessageArgs), Move(MoveArgs), Update(UpdateArgs), + Events(EventArgs), } #[derive(Args)] @@ -78,6 +79,11 @@ pub struct UpdateArgs { speakers: Option, } +#[derive(Args)] +pub struct EventArgs { + pub event: Vec, +} + impl FetchArgs { pub fn want_channel(&self) -> bool { self.channel.is_some() @@ -89,14 +95,14 @@ impl FetchArgs { pub fn channel(&self, connection: &mut Telnet) -> Result, String> { if let Some(channel) = &self.channel { - wrappers::find_channel(connection, channel, self.strict_channel) + wrappers::find_channel(connection, "channel_name", channel, self.strict_channel) } else { Err("No channel specified.".to_string()) } } pub fn client(&self, connection: &mut Telnet) -> Result, String> { if let Some(client) = &self.client { - wrappers::find_client(connection, client, self.strict_client) + wrappers::find_client(connection, "client_nickname", client, self.strict_client) } else { Err("No client specified.".to_string()) } @@ -108,7 +114,7 @@ impl MessageArgs { if self.server { Ok(SendTextMessageTarget::Server) } else if let Some(client) = &self.client { - if let Some(client) = wrappers::find_client(connection, client, self.strict_client)? { + if let Some(client) = wrappers::find_client(connection, "client_nickname", client, self.strict_client)? { return Ok(SendTextMessageTarget::Client(client)); } return Err("Could not find client.".to_string()); @@ -120,12 +126,12 @@ impl MessageArgs { impl MoveArgs { pub fn channel(&self, connection: &mut Telnet) -> Result, String> { - wrappers::find_channel(connection, &self.channel, self.strict_channel) + wrappers::find_channel(connection, "channel_name", &self.channel, self.strict_channel) } pub fn client(&self, connection: &mut Telnet) -> Result, String> { match &self.client { Some(client) => { - wrappers::find_client(connection, client, self.strict_client) + wrappers::find_client(connection, "client_nickname", client, self.strict_client) } None => { match wrappers::find_self(connection) { @@ -139,25 +145,25 @@ impl MoveArgs { impl UpdateArgs { pub fn to_parameter_list(&self) -> ParameterList { - let mut params: ParameterList = Vec::new(); + let mut params: ParameterList = ParameterList::new(); if let Some(name) = &self.name { - params.push(Parameter::new("client_nickname", name)); + params.insert(String::from("client_nickname"), name.clone()); } if let Some(away) = &self.away { - params.push(Parameter::new("client_away_message", away)); - params.push(Parameter::new("client_away", "1")); + params.insert(String::from("client_away_message"), away.clone()); + params.insert(String::from("client_away"), String::from("1")); } if self.back { - params.push(Parameter::new("client_away", "0")); + params.insert(String::from("client_away"), String::from("0")); } if let Some(microphone) = self.microphone { - params.push(Parameter::new("client_input_muted", &u8::from(!microphone).to_string())); + params.insert(String::from("client_input_muted"), u8::from(!microphone).to_string()); } if let Some(speakers) = self.speakers { - params.push(Parameter::new("client_output_muted", &u8::from(!speakers).to_string())); + params.insert(String::from("client_output_muted"), u8::from(!speakers).to_string()); } params diff --git a/src/commands.rs b/src/commands.rs index 4e2a323..204e455 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,8 +1,9 @@ use crate::utils::SendTextMessageTarget; use telnet::Event::Data; use telnet::Telnet; +use crate::models::EventType; -use crate::parameter::{Parameter, ParameterList}; +use crate::parameter::{Parameter, parameters_to_string, ParameterList, parameter_to_string, parameter_list_to_string}; use crate::response::Response; fn to_single_response(resp: Response) -> Response { @@ -48,7 +49,7 @@ fn send_command(connection: &mut Telnet, command: &str, skip_ok: bool) -> Result read_response(connection, skip_ok, String::new()) } -fn read_response(connection: &mut Telnet, skip_ok: bool, mut buffer: String) -> Result { +pub fn read_response(connection: &mut Telnet, skip_ok: bool, mut buffer: String) -> Result { let (response_str, buffer) = read_response_buffer(connection, &mut buffer)?; match Response::try_from(response_str) { @@ -93,18 +94,22 @@ pub fn whoami(connection: &mut Telnet) -> Result { pub fn clientmove(connection: &mut Telnet, cid: &i32, clid_list: Vec<&i32>) -> Result { let clid_param_list = clid_list .into_iter() - .map(|clid| Parameter::new("clid", &clid.to_string())) + .map(|clid| (String::from("clid"), clid.to_string())) .collect::>(); - let clid_str = Parameter::list_to_string_sep(clid_param_list, "|"); + let clid_str = parameters_to_string(clid_param_list, "|"); send_command(connection, &format!("clientmove cid={} {}", cid, clid_str), false) } pub fn clientupdate(connection: &mut Telnet, parameters: ParameterList) -> Result { - let parameters_str = Parameter::list_to_string(parameters); + let parameters_str = parameter_list_to_string(parameters, " "); send_command(connection, &format!("clientupdate {}", parameters_str), false) } pub fn sendtextmessage(connection: &mut Telnet, target: SendTextMessageTarget, msg: String) -> Result { - let msg = String::from(Parameter::new("msg", &msg)); + let msg = parameter_to_string((String::from("msg"), msg)); send_command(connection, &format!("sendtextmessage {} {}", msg, String::from(target)), false) } + +pub fn clientnotifyregister(connection: &mut Telnet, schandlerid: u32, event: EventType) -> Result { + send_command(connection, &format!("clientnotifyregister schandlerid={} event={}", schandlerid, String::from(&event)), false) +} diff --git a/src/main.rs b/src/main.rs index 8899b28..6af36ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,9 @@ use std::process::exit; use telnet::Telnet; use crate::cli::Commands; -use crate::models::Channel; +use crate::models::{Channel, Event}; use crate::models::Client; +use crate::response::Response; mod wrappers; mod commands; @@ -49,8 +50,6 @@ fn main() { wrappers::skip_welcome(&mut connection); wrappers::login(&mut connection); - - // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd match cli { @@ -154,5 +153,35 @@ fn main() { } } } + + Commands::Events(args) => { + for event in args.event { + if commands::clientnotifyregister(&mut connection, 1, event).is_err() { + println!("Failed to register event listener."); + exit(1); + } + } + + loop { + match commands::read_response(&mut connection, true, String::new()) { + Ok(response) => { + if let Response::Event(event_type, params) = response { + + let event = Event::new(&mut connection, event_type, params); + match serde_json::to_string(&event) { + Ok(json) => println!("{}", json), + Err(_) => { + // TODO: Handle error + } + } + + } + } + Err(_) => { + // TODO: Handle error + } + } + } + } } } \ No newline at end of file diff --git a/src/models/channel.rs b/src/models/channel.rs index 731dcf5..ed7f5f7 100644 --- a/src/models/channel.rs +++ b/src/models/channel.rs @@ -1,8 +1,9 @@ use std::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; use crate::parameter::{parameter_find, ParameterList}; -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct Channel { pub cid: i32, pub pid: i32, @@ -23,22 +24,20 @@ impl From for Channel { Channel { cid: parameter_find(&value, "cid") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), pid: parameter_find(&value, "pid") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), channel_order: parameter_find(&value, "channel_order") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), channel_name: parameter_find(&value, "channel_name") - .unwrap_or_default() - .value, + .unwrap_or_default(), channel_topic: parameter_find(&value, "channel_topic") - .unwrap_or_default() - .value, + .unwrap_or_default(), channel_flag_are_subscribed: parameter_find(&value, "channel_flag_are_subscribed") .unwrap_or_default() - .to_i32(0) == 1, + .parse::().unwrap_or(0) == 1, } } } diff --git a/src/models/client.rs b/src/models/client.rs index 25405e0..fe38e06 100644 --- a/src/models/client.rs +++ b/src/models/client.rs @@ -1,8 +1,9 @@ use std::fmt::{Display, Formatter}; +use serde::{Deserialize, Serialize}; use crate::parameter::{parameter_find, ParameterList}; -#[derive(Debug)] +#[derive(Serialize, Deserialize, Debug)] pub struct Client { pub cid: i32, pub clid: i32, @@ -22,19 +23,18 @@ impl From for Client { Client { cid: parameter_find(&value, "cid") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), clid: parameter_find(&value, "clid") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), client_database_id: parameter_find(&value, "client_database_id") .unwrap_or_default() - .to_i32(-1), + .parse::().unwrap_or(-1), client_nickname: parameter_find(&value, "client_nickname") - .unwrap_or_default() - .value, + .unwrap_or_default(), client_type: parameter_find(&value, "channel_topic") .unwrap_or_default() - .to_i32(0), + .parse::().unwrap_or(0), } } } \ No newline at end of file diff --git a/src/models/event.rs b/src/models/event.rs index d18f943..1d383c8 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -1,7 +1,16 @@ use std::fmt::{Display, Formatter}; +use std::str::FromStr; -#[derive(Debug)] -pub enum Event { +use serde::{Deserialize, Serialize}; +use telnet::Telnet; + +use crate::models::{Channel, Client}; +use crate::parameter::{parameter_find, ParameterList}; +use crate::wrappers; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum EventType { + Any, ChannelList, ChannelListFinished, NotifyTalkStatusChange, @@ -16,6 +25,7 @@ pub enum Event { NotifyClientChatClosed, NotifyClientChatComposing, NotifyClientUpdated, + NotifyClientChannelGroupChanged, NotifyClientIds, NotifyClientDBIDFromUid, NotifyClientNameFromUid, @@ -33,51 +43,151 @@ pub enum Event { NotifyConnectStatusChange, } -impl TryFrom<&str> for Event { +#[derive(Serialize, Deserialize, Debug)] +pub struct Event { + pub event_type: EventType, + pub params: ParameterList, + pub channel: Option, + pub client: Option, +} + +impl TryFrom<&str> for EventType { type Error = String; //noinspection SpellCheckingInspection fn try_from(event_type: &str) -> Result { - match event_type { - "channellist" => Ok(Event::ChannelList), - "channellistfinished" => Ok(Event::ChannelListFinished), - "notifytalkstatuschange" => Ok(Event::NotifyTalkStatusChange), - "notifymessage" => Ok(Event::NotifyMessage), - "notifymessagelist" => Ok(Event::NotifyMessageList), - "notifycomplainlist" => Ok(Event::NotifyComplainList), - "notifybanlist" => Ok(Event::NotifyBanList), - "notifyclientmoved" => Ok(Event::NotifyClientMoved), - "notifyclientleftview" => Ok(Event::NotifyClientLeftView), - "notifycliententerview" => Ok(Event::NotifyClientEnterView), - "notifyclientpoke" => Ok(Event::NotifyClientPoke), - "notifyclientchatclosed" => Ok(Event::NotifyClientChatClosed), - "notifyclientchatcomposing" => Ok(Event::NotifyClientChatComposing), - "notifyclientupdated" => Ok(Event::NotifyClientUpdated), - "notifyclientids" => Ok(Event::NotifyClientIds), - "notifyclientdbidfromuid" => Ok(Event::NotifyClientDBIDFromUid), - "notifyclientnamefromuid" => Ok(Event::NotifyClientNameFromUid), - "notifyclientnamefromdbid" => Ok(Event::NotifyClientNameFromDBID), - "notifyclientuidfromclid" => Ok(Event::NotifyClientUidFromClid), - "notifyconnectioninfo" => Ok(Event::NotifyConnectionInfo), - "notifychannelcreated" => Ok(Event::NotifyChannelCreated), - "notifychanneledited" => Ok(Event::NotifyChannelEdited), - "notifychanneldeleted" => Ok(Event::NotifyChannelDeleted), - "notifychannelmoved" => Ok(Event::NotifyChannelMoved), - "notifyserveredited" => Ok(Event::NotifyServerEdited), - "notifyserverupdated" => Ok(Event::NotifyServerUpdated), - "notifytextmessage" => Ok(Event::NotifyTextMessage), + match event_type.to_lowercase().as_str() { + "any" => Ok(EventType::Any), + "channellist" => Ok(EventType::ChannelList), + "channellistfinished" => Ok(EventType::ChannelListFinished), + "notifytalkstatuschange" => Ok(EventType::NotifyTalkStatusChange), + "notifymessage" => Ok(EventType::NotifyMessage), + "notifymessagelist" => Ok(EventType::NotifyMessageList), + "notifycomplainlist" => Ok(EventType::NotifyComplainList), + "notifybanlist" => Ok(EventType::NotifyBanList), + "notifyclientmoved" => Ok(EventType::NotifyClientMoved), + "notifyclientleftview" => Ok(EventType::NotifyClientLeftView), + "notifycliententerview" => Ok(EventType::NotifyClientEnterView), + "notifyclientpoke" => Ok(EventType::NotifyClientPoke), + "notifyclientchatclosed" => Ok(EventType::NotifyClientChatClosed), + "notifyclientchatcomposing" => Ok(EventType::NotifyClientChatComposing), + "notifyclientupdated" => Ok(EventType::NotifyClientUpdated), + "notifyclientchannelgroupchanged" => Ok(EventType::NotifyClientChannelGroupChanged), + "notifyclientids" => Ok(EventType::NotifyClientIds), + "notifyclientdbidfromuid" => Ok(EventType::NotifyClientDBIDFromUid), + "notifyclientnamefromuid" => Ok(EventType::NotifyClientNameFromUid), + "notifyclientnamefromdbid" => Ok(EventType::NotifyClientNameFromDBID), + "notifyclientuidfromclid" => Ok(EventType::NotifyClientUidFromClid), + "notifyconnectioninfo" => Ok(EventType::NotifyConnectionInfo), + "notifychannelcreated" => Ok(EventType::NotifyChannelCreated), + "notifychanneledited" => Ok(EventType::NotifyChannelEdited), + "notifychanneldeleted" => Ok(EventType::NotifyChannelDeleted), + "notifychannelmoved" => Ok(EventType::NotifyChannelMoved), + "notifyserveredited" => Ok(EventType::NotifyServerEdited), + "notifyserverupdated" => Ok(EventType::NotifyServerUpdated), + "notifytextmessage" => Ok(EventType::NotifyTextMessage), "notifycurrentserverconnectionchanged" => { - Ok(Event::NotifyCurrentServerConnectionChanged) + Ok(EventType::NotifyCurrentServerConnectionChanged) } - "notifyconnectstatuschange" => Ok(Event::NotifyConnectStatusChange), - _ => Err(String::from("Unknown event type.")), + "notifyconnectstatuschange" => Ok(EventType::NotifyConnectStatusChange), + _ => Err(format!("Unknown event type: {}", event_type)), } } } +impl FromStr for EventType { + type Err = String; -impl Display for Event { + fn from_str(s: &str) -> Result { + EventType::try_from(s) + } +} + +impl From<&EventType> for String { + //noinspection SpellCheckingInspection + fn from(value: &EventType) -> Self { + match value { + EventType::Any => String::from("any"), + EventType::ChannelList => String::from("channellist"), + EventType::ChannelListFinished => String::from("channellistfinished"), + EventType::NotifyTalkStatusChange => String::from("notifytalkstatuschange"), + EventType::NotifyMessage => String::from("notifymessage"), + EventType::NotifyMessageList => String::from("notifymessagelist"), + EventType::NotifyComplainList => String::from("notifycomplainlist"), + EventType::NotifyBanList => String::from("notifybanlist"), + EventType::NotifyClientMoved => String::from("notifyclientmoved"), + EventType::NotifyClientLeftView => String::from("notifyclientleftview"), + EventType::NotifyClientEnterView => String::from("notifycliententerview"), + EventType::NotifyClientPoke => String::from("notifyclientpoke"), + EventType::NotifyClientChatClosed => String::from("notifyclientchatclosed"), + EventType::NotifyClientChatComposing => String::from("notifyclientchatcomposing"), + EventType::NotifyClientChannelGroupChanged => String::from("notifyclientchannelgroupchanged"), + EventType::NotifyClientUpdated => String::from("notifyclientupdated"), + EventType::NotifyClientIds => String::from("notifyclientids"), + EventType::NotifyClientDBIDFromUid => String::from("notifyclientdbidfromuid"), + EventType::NotifyClientNameFromUid => String::from("notifyclientnamefromuid"), + EventType::NotifyClientNameFromDBID => String::from("notifyclientnamefromdbid"), + EventType::NotifyClientUidFromClid => String::from("notifyclientuidfromclid"), + EventType::NotifyConnectionInfo => String::from("notifyconnectioninfo"), + EventType::NotifyChannelCreated => String::from("notifychannelcreated"), + EventType::NotifyChannelEdited => String::from("notifychanneledited"), + EventType::NotifyChannelDeleted => String::from("notifychanneldeleted"), + EventType::NotifyChannelMoved => String::from("notifychannelmoved"), + EventType::NotifyServerEdited => String::from("notifyserveredited"), + EventType::NotifyServerUpdated => String::from("notifyserverupdated"), + EventType::NotifyTextMessage => String::from("notifytextmessage"), + EventType::NotifyCurrentServerConnectionChanged => { + String::from("notifycurrentserverconnectionchanged") + } + EventType::NotifyConnectStatusChange => String::from("notifyconnectstatuschange"), + } + } +} + +impl Display for EventType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + let event_str: String = self.into(); + write!(f, "{}", event_str) + } +} + +impl EventType { + pub fn get_channel(&self, connection: &mut Telnet, params: &ParameterList) -> Option { + match self { + EventType::NotifyClientMoved => { + let id = parameter_find(params, "ctid")? + .parse::().unwrap_or(0); + wrappers::find_channel(connection, "cid", &id.to_string(), true).unwrap().or(None) + } + _ => None, + } + } + + pub fn get_client(&self, connection: &mut Telnet, params: &ParameterList) -> Option { + match self { + EventType::NotifyClientMoved => { + let id = parameter_find(params, "clid")? + .parse::().unwrap_or(0); + wrappers::find_client(connection, "clid", &id.to_string(), true).unwrap().or(None) + } + EventType::NotifyClientEnterView => { + Some(Client::from(params.clone())) + } + _ => None, + } + } +} + +impl Event { + pub fn new(connection: &mut Telnet, event_type: EventType, params: ParameterList) -> Event { + let channel = event_type.get_channel(connection, ¶ms); + let client = event_type.get_client(connection, ¶ms); + + Event { + event_type, + params, + channel, + client, + } } } \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs index a37ebaf..6d919ab 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -4,4 +4,4 @@ mod event; pub use channel::Channel; pub use client::Client; -pub use event::Event; \ No newline at end of file +pub use event::{EventType, Event}; \ No newline at end of file diff --git a/src/parameter.rs b/src/parameter.rs index 2c7623e..6424ecb 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -1,36 +1,38 @@ -use std::fmt::{Debug, Display, Formatter}; +use std::collections::HashMap; use crate::utils::{decode_value, encode_value}; -#[derive(Clone)] -pub struct Parameter { - pub name: String, - pub value: String, +pub type ParameterList = HashMap; +pub type Parameter = (String, String); + +pub fn parameter_find(params: &ParameterList, name: &str) -> Option { + params.get(name).map(|value| value.clone()) } -pub type ParameterList = Vec; - -pub fn parameter_find(params: &Vec, name: &str) -> Option { - for param in params { - if param.name == name { - return Some(param.clone()); +pub fn parameter_list_has(params: &ParameterList, key: &str, value: &str, strict: bool) -> bool { + if let Some(check_value) = params.get(key) { + if check_value == value && strict { + return true; + } + if check_value.contains(value) && !strict { + return true; } } - None + return false; } -pub fn parameter_list_find(param_lists: &Vec, name: &str, value: &str, strict: bool) -> Option { +pub fn parameter_list_find(param_lists: &Vec, key: &str, value: &str, strict: bool) -> Option { for params in param_lists { - if params.iter().any(|param| param.is(name, value, strict)) { + if parameter_list_has(params, key, value, strict) { return Some(params.clone()); } } None } -pub fn parameter_list_find_all(param_lists: &Vec, name: &str, value: &str, strict: bool) -> Vec { +pub fn parameter_list_find_all(param_lists: &Vec, key: &str, value: &str, strict: bool) -> Vec { let mut found = Vec::new(); for params in param_lists { - if params.iter().any(|param| param.is(name, value, strict)) { + if parameter_list_has(params, key, value, strict) { found.push(params.clone()) } } @@ -40,82 +42,31 @@ pub fn parameter_list_find_all(param_lists: &Vec, name: &str, val pub fn parameter_parse(params_str: &str) -> ParameterList { let parts: Vec<&str> = params_str.split(' ').collect(); - let mut response_params = Vec::new(); + let mut response_params = ParameterList::new(); parts.iter().for_each(|part| { - response_params.push(Parameter::from(part.split_once('=').unwrap())); + let (key, value) = part.split_once('=').unwrap_or((part, "1")); + response_params.insert(key.to_string(), decode_value(value)); }); response_params } -impl From<(&str, &str)> for Parameter { - fn from(param: (&str, &str)) -> Parameter { - Parameter::new(param.0, param.1) - } +pub fn parameter_to_string(param: Parameter) -> String { + format!("{}={}", param.0, encode_value(¶m.1)) } -impl Parameter { - pub fn new(name: &str, value: &str) -> Parameter { - Parameter { - name: String::from(name), - value: decode_value(value) - } - } - - pub fn is(&self, name: &str, value: &str, strict: bool) -> bool { - if self.name != name { - return false; - } - if self.value != value && strict { - return false; - } - if !self.value.contains(value) && !strict { - return false; - } - true - } - - pub fn to_i32(&self, default: i32) -> i32 { - self.value.parse::().unwrap_or(default) - } - - pub fn list_to_string(parameter_list: ParameterList) -> String { - parameter_list - .into_iter() - .map(String::from) - .collect::>() - .join(" ") - } - - pub fn list_to_string_sep(parameter_list: ParameterList, sep: &str) -> String { - parameter_list - .into_iter() - .map(String::from) - .collect::>() - .join(sep) - } +pub fn parameter_list_to_string(parameter_list: ParameterList, sep: &str) -> String { + parameter_list + .into_iter() + .map(|param| parameter_to_string(param)) + .collect::>() + .join(sep) } -impl Display for Parameter { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}={}", self.name, self.value) - } -} - -impl Debug for Parameter { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}={}", self.name, self.value) - } -} - -impl Default for Parameter { - fn default() -> Self { - Parameter::new("", "") - } -} - -impl From for String { - fn from(value: Parameter) -> Self { - format!("{}={}", value.name, encode_value(&value.value)) - } +pub fn parameters_to_string(parameters: Vec, sep: &str) -> String { + parameters + .into_iter() + .map(|param| parameter_to_string(param)) + .collect::>() + .join(sep) } \ No newline at end of file diff --git a/src/response.rs b/src/response.rs index 261ff24..aed60d2 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,5 +1,5 @@ use std::fmt::{Debug, Display, Formatter}; -use crate::models::Event; +use crate::models::EventType; use crate::parameter::*; @@ -7,12 +7,12 @@ pub enum Response { Ok, Data(ParameterList), DataList(Vec), - Event(Event, ParameterList), + Event(EventType, ParameterList), } pub enum ResponseType { Error, - Event(Event), + Event(EventType), Data, Unknown, } @@ -41,7 +41,7 @@ fn get_response_type(type_str: &str) -> Result { return Ok(ResponseType::Error); } if type_str.starts_with("notify") || type_str == "channellist" || type_str == "channellistfinished" { - let event_type = Event::try_from(type_str)?; + let event_type = EventType::try_from(type_str)?; return Ok(ResponseType::Event(event_type)); } @@ -120,16 +120,16 @@ impl Display for Response { write!(f, "Ok") } Response::Data(params) => { - for param in params { - write!(f, "{};", param)?; + for param in params.clone() { + write!(f, "{};", parameter_to_string(param))?; } Ok(()) } Response::DataList(params_list) => { for params in params_list { write!(f, "[")?; - for param in params { - write!(f, "{};", param)?; + for param in params.clone() { + write!(f, "{};", parameter_to_string(param))?; } write!(f, "]")?; } @@ -137,8 +137,8 @@ impl Display for Response { } Response::Event(event, params) => { write!(f, "Event: {}", event)?; - for param in params { - write!(f, "{};", param)?; + for param in params.clone() { + write!(f, "{};", parameter_to_string(param))?; } Ok(()) } @@ -151,14 +151,13 @@ impl ResponseError { self.id == 0 } - fn create_error(params: &Vec) -> ResponseError { + fn create_error(params: &ParameterList) -> ResponseError { ResponseError { id: parameter_find(params, "id") - .unwrap_or_else(|| Parameter::new("id", "-1")) - .to_i32(-1), + .unwrap_or_else(|| String::from("-1")) + .parse::().unwrap_or(-1), msg: parameter_find(params, "msg") - .unwrap_or_else(|| Parameter::new("msg", "Unknown error.")) - .value, + .unwrap_or_else(|| String::from("Unknown error.")) } } } \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 766eb23..e21547f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use crate::models::Client; -use crate::parameter::Parameter; +use crate::parameter::parameters_to_string; pub fn decode_value(value: &str) -> String { value @@ -32,9 +32,9 @@ impl From for String { _ => String::from("0"), }; - Parameter::list_to_string(vec![ - Parameter::new("targetmode", target_mode), - Parameter::new("target", &target) - ]) + parameters_to_string(vec![ + (String::from("targetmode"), String::from(target_mode)), + (String::from("target"), target) + ], " ") } } \ No newline at end of file diff --git a/src/wrappers.rs b/src/wrappers.rs index 0effadc..9c50680 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -57,10 +57,10 @@ pub fn get_channels(connection: &mut Telnet, spacers: bool) -> Result Result, String> { +pub fn find_channel(connection: &mut Telnet, key: &str, value: &str, strict: bool) -> Result, String> { match commands::channellist(connection)? { Response::DataList(parameter_lists) => { - match parameter::parameter_list_find(¶meter_lists, "channel_name", name, strict) { + match parameter::parameter_list_find(¶meter_lists, key, value, strict) { Some(params) => { Ok(Some(Channel::from(params))) } @@ -88,10 +88,10 @@ pub fn get_clients(connection: &mut Telnet) -> Result, String> { } } -pub fn find_client(connection: &mut Telnet, name: &str, strict: bool) -> Result, String> { +pub fn find_client(connection: &mut Telnet, key: &str, value: &str, strict: bool) -> Result, String> { match commands::clientlist(connection)? { Response::DataList(parameter_lists) => { - match parameter::parameter_list_find(¶meter_lists, "client_nickname", name, strict) { + match parameter::parameter_list_find(¶meter_lists, key, value, strict) { Some(params) => { Ok(Some(Client::from(params))) } @@ -122,7 +122,7 @@ fn get_self_clid(connection: &mut Telnet) -> Result { Response::Data(params) => { match parameter::parameter_find(¶ms, "clid") { None => Err(String::from("Could not find clid in models from Teamspeak.")), - Some(param) => Ok(param.value) + Some(param) => Ok(param) } } _ => Err(String::from("Received unexpected models from Teamspeak for whoami.")) diff --git a/ts-control b/ts-control index 3ce6769..5f26108 100755 --- a/ts-control +++ b/ts-control @@ -8,7 +8,8 @@ away not away back message -message-user" +message-user +events-move" _ts_control_get_entity() { entity=$(_ts_control_single_or_dmenu "$(teamspeak-query-lib "$1s")" "$2") @@ -97,4 +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"' \ + | xargs -I{} notify-send "TS3 movement" "{}" esac