From 244a7073fe9b556512e7fdeec65c0c6b97b0d0a0 Mon Sep 17 00:00:00 2001 From: Tobias Reisinger Date: Fri, 17 Nov 2023 17:32:15 +0100 Subject: [PATCH] Add fetch-channel command --- Makefile | 8 ++++ src/cli.rs | 30 +++++++++++++-- src/commands.rs | 3 +- src/main.rs | 82 ++++++++++++++++++++++++++--------------- src/parameter.rs | 35 +++++++++++++----- src/response.rs | 9 ++++- src/response_classes.rs | 13 +++++++ src/utils.rs | 29 ++++++++++++++- ts-control | 28 ++++++++++---- 9 files changed, 183 insertions(+), 54 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..318c7c9 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +.PHONY: run + +build: + @cargo build + +run: build + @PATH=$(PWD)/target/debug:$(PATH) ./ts-control + diff --git a/src/cli.rs b/src/cli.rs index 646a7fe..1a4cf43 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -31,7 +31,12 @@ pub struct ChannelsArgs { pub struct FetchArgs { #[arg(long)] strict_client: bool, - client: String, + #[arg(long)] + strict_channel: bool, + #[arg(long)] + channel: Option, + #[arg(long)] + client: Option, } #[derive(Args)] @@ -58,12 +63,29 @@ pub struct UpdateArgs { speakers: Option, } - impl FetchArgs { - pub fn client(&self, connection: &mut Telnet) -> Result, String> { - utils::find_client(connection, &self.client, self.strict_client) + pub fn want_channel(&self) -> bool { + self.channel.is_some() } + pub fn want_client(&self) -> bool { + self.client.is_some() + } + + pub fn channel(&self, connection: &mut Telnet) -> Result, String> { + if let Some(channel) = &self.channel { + utils::find_channel(connection, 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 { + utils::find_client(connection, client, self.strict_client) + } else { + Err("No client specified.".to_string()) + } + } } impl MoveArgs { diff --git a/src/commands.rs b/src/commands.rs index 4cfa611..39630a2 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -57,8 +57,7 @@ fn read_response(connection: &mut Telnet, skip_ok: bool, mut buffer: String) -> if skip_ok { read_response(connection, skip_ok, buffer) } else { - // empty Ok response - Ok(Response::Data(Vec::new())) + Ok(Response::Ok) } } else { Err(format!("Received error response from Teamspeak: {} ({})", err.msg, err.id)) diff --git a/src/main.rs b/src/main.rs index e363b32..861c69a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,29 @@ mod cli; use std::process::exit; use telnet::Telnet; use crate::cli::Commands; +use crate::response_classes::{ResponseChannel, ResponseClient}; + +fn channel_or_exit(channel_res: Result, String>) -> ResponseChannel { + channel_res.unwrap_or_else(|err| { + println!("Failed to find channel: {}", err); + exit(1); + }) + .unwrap_or_else(|| { + println!("Failed to find channel."); + exit(1); + }) +} + +fn client_or_exit(client_res: Result, String>) -> ResponseClient { + client_res.unwrap_or_else(|err| { + println!("Failed to find client: {}", err); + exit(1); + }) + .unwrap_or_else(|| { + println!("Failed to find client."); + exit(1); + }) +} fn main() { @@ -55,42 +78,43 @@ fn main() { } Commands::Fetch(args) => { - let client = args.client(&mut connection).unwrap_or_else(|err| { - println!("Failed to find client for move: {}", err); + if args.want_client() && args.want_channel() { + println!("Fetching both clients and channels is not supported."); exit(1); - }) - .unwrap_or_else(|| { - println!("Failed to find client for move."); - exit(1); - }); + } + if !args.want_client() && !args.want_channel() { + println!("No clients or channels specified."); + exit(1); + } - match utils::fetch_client(&mut connection, &[client]) { - Ok(resp) => println!("Successfully fetched client: {}", resp), - Err(msg) => { - println!("Failed to fetch client: {}", msg); - exit(1); + if args.want_client() { + let client = client_or_exit(args.client(&mut connection)); + + match utils::fetch_client(&mut connection, &[client]) { + Ok(_) => println!("Successfully fetched client."), + Err(msg) => { + println!("Failed to fetch client: {}", msg); + exit(1); + } + } + } + + if args.want_channel() { + let channel = channel_or_exit(args.channel(&mut connection)); + + match utils::fetch_channel(&mut connection, channel) { + Ok(_) => println!("Successfully fetched channel."), + Err(msg) => { + println!("Failed to fetch channel: {}", msg); + exit(1); + } } } } Commands::Move(args) => { - let channel = args.channel(&mut connection).unwrap_or_else(|err| { - println!("Failed to find channel for move: {}", err); - exit(1); - }) - .unwrap_or_else(|| { - println!("Failed to find channel for move."); - exit(1); - }); - - let client = args.client(&mut connection).unwrap_or_else(|err| { - println!("Failed to find client for move: {}", err); - exit(1); - }) - .unwrap_or_else(|| { - println!("Failed to find client for move."); - exit(1); - }); + let channel = channel_or_exit(args.channel(&mut connection)); + let client = client_or_exit(args.client(&mut connection)); match utils::move_client(&mut connection, &channel, &[client]) { Ok(resp) => println!("Successfully moved client: {}", resp), diff --git a/src/parameter.rs b/src/parameter.rs index d1aae1c..e574968 100644 --- a/src/parameter.rs +++ b/src/parameter.rs @@ -18,21 +18,21 @@ pub fn parameter_find(params: &Vec, name: &str) -> Option } pub fn parameter_list_find(param_lists: &Vec, name: &str, value: &str, strict: bool) -> Option { for params in param_lists { - for param in params { - if param.name != name { - continue; - } - if param.value != value && strict { - continue; - } - if !param.value.contains(value) && !strict { - continue; - } + if params.iter().position(|param| param.is(name, value, strict)).is_some() { return Some(params.clone()); } } None } +pub fn parameter_list_find_all(param_lists: &Vec, name: &str, value: &str, strict: bool) -> Vec { + let mut found = Vec::new(); + for params in param_lists { + if params.iter().position(|param| param.is(name, value, strict)).is_some() { + found.push(params.clone()) + } + } + found +} pub fn parameter_parse(params_str: &str) -> ParameterList { let parts: Vec<&str> = params_str.split(' ').collect(); @@ -63,6 +63,21 @@ impl Parameter { } } + pub fn is(&self, name: &str, value: &str, strict: bool) -> bool { + if self.name != name { + false + } + else if self.value != value && strict { + false + } + else if !self.value.contains(value) && !strict { + false + } + else { + true + } + } + pub fn to_i32(&self, default: i32) -> i32 { self.value.parse::().unwrap_or(default) } diff --git a/src/response.rs b/src/response.rs index e728338..09f362f 100644 --- a/src/response.rs +++ b/src/response.rs @@ -2,6 +2,7 @@ use std::fmt::{Debug, Display, Formatter}; use crate::parameter::*; pub enum Response { + Ok, Data(ParameterList), DataList(Vec), } @@ -36,6 +37,9 @@ impl TryFrom for Response { impl Debug for Response { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + Response::Ok => { + write!(f, "Ok") + } Response::Data(params) => { write!(f, "Data:")?; write!(f, "{:?};", params)?; @@ -48,13 +52,16 @@ impl Debug for Response { } Ok(()) } - } + } } } impl Display for Response { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { + Response::Ok => { + write!(f, "Ok") + } Response::Data(params) => { for param in params { write!(f, "{};", param)?; diff --git a/src/response_classes.rs b/src/response_classes.rs index 0006a54..ac11043 100644 --- a/src/response_classes.rs +++ b/src/response_classes.rs @@ -1,3 +1,4 @@ +use std::fmt::{Display, Formatter}; use crate::parameter::{parameter_find, ParameterList}; #[derive(Debug)] @@ -10,6 +11,12 @@ pub struct ResponseChannel { pub channel_flag_are_subscribed: bool, } +impl Display for ResponseChannel { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.channel_name, self.cid) + } +} + impl From for ResponseChannel { fn from(value: ParameterList) -> Self { ResponseChannel { @@ -83,6 +90,12 @@ pub struct ResponseClient { pub client_type: i32, } +impl Display for ResponseClient { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", self.client_nickname, self.clid) + } +} + impl From for ResponseClient { fn from(value: ParameterList) -> Self { ResponseClient { diff --git a/src/utils.rs b/src/utils.rs index 6a56058..33a6f44 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,7 +4,7 @@ use std::time::Duration; use telnet::Event::TimedOut; use crate::{commands, parameter}; -use crate::parameter::ParameterList; +use crate::parameter::{parameter_list_find_all, ParameterList}; use crate::response::Response; use crate::response_classes::{ResponseChannel, ResponseClient}; @@ -121,6 +121,24 @@ pub fn find_client(connection: &mut Telnet, name: &str, strict: bool) -> Result< } } +pub fn get_channel_clients(connection: &mut Telnet, channel: &ResponseChannel) -> Result, String> { + match commands::clientlist(connection) { + Ok(response) => { + match response { + Response::DataList(parameter_lists) => { + let mut clients: Vec = Vec::new(); + for client_params in parameter_list_find_all(¶meter_lists, "cid", &channel.cid.to_string(), true) { + clients.push(ResponseClient::from(client_params)); + } + Ok(clients) + } + _ => Err(String::from("Received unexpected response from Teamspeak.")) + } + } + Err(msg) => Err(msg) + } +} + fn get_self_clid(connection: &mut Telnet) -> Result { match commands::whoami(connection) { Ok(response) => { @@ -168,6 +186,15 @@ pub fn fetch_client(connection: &mut Telnet, clients: &[ResponseClient]) -> Resu commands::clientmove(connection, &cid, clid_list) } +pub fn fetch_channel(connection: &mut Telnet, channel: ResponseChannel) -> Result { + let cid = find_self(connection)?.cid; + + let clients = get_channel_clients(connection, &channel)?; + let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect(); + + commands::clientmove(connection, &cid, clid_list) +} + pub fn move_client(connection: &mut Telnet, channel: &ResponseChannel, clients: &[ResponseClient]) -> Result { let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect(); diff --git a/ts-control b/ts-control index fcfbb21..f2a2f7c 100755 --- a/ts-control +++ b/ts-control @@ -1,6 +1,13 @@ #!/usr/bin/env sh -action=$(printf "move\nfetch\naway\nnot away\nback" | $DMENU) +actions="move +fetch-client +fetch-channel +away +not away +back" +action=$(echo "$actions" | $DMENU) + ts_control_move_self() { channel=$(teamspeak-query-lib channels | $DMENU) @@ -11,16 +18,23 @@ ts_control_move_self() { return 0 } +ts_control_fetch() { + target=$(teamspeak-query-lib "$1s" | $DMENU) + if [ -z "$target" ]; then + exit 1 + fi + teamspeak-query-lib fetch "--strict-$1" "--$1" "$target" +} + case $action in "move") ts_control_move_self ;; - "fetch") - client=$(teamspeak-query-lib clients | $DMENU) - if [ -z "$client" ]; then - exit 1 - fi - teamspeak-query-lib fetch --strict-client "$client" + "fetch-client") + ts_control_fetch client + ;; + "fetch-channel") + ts_control_fetch channel ;; "not away") teamspeak-query-lib move "Not Away From Keyboard"