Add fetch-channel command
This commit is contained in:
		
							parent
							
								
									970c1ee2c2
								
							
						
					
					
						commit
						244a7073fe
					
				
					 9 changed files with 183 additions and 54 deletions
				
			
		
							
								
								
									
										8
									
								
								Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | .PHONY: run | ||||||
|  | 
 | ||||||
|  | build: | ||||||
|  | 	@cargo build | ||||||
|  | 
 | ||||||
|  | run: build | ||||||
|  | 	@PATH=$(PWD)/target/debug:$(PATH) ./ts-control | ||||||
|  | 
 | ||||||
							
								
								
									
										30
									
								
								src/cli.rs
									
										
									
									
									
								
							
							
						
						
									
										30
									
								
								src/cli.rs
									
										
									
									
									
								
							|  | @ -31,7 +31,12 @@ pub struct ChannelsArgs { | ||||||
| pub struct FetchArgs { | pub struct FetchArgs { | ||||||
|     #[arg(long)] |     #[arg(long)] | ||||||
|     strict_client: bool, |     strict_client: bool, | ||||||
|     client: String, |     #[arg(long)] | ||||||
|  |     strict_channel: bool, | ||||||
|  |     #[arg(long)] | ||||||
|  |     channel: Option<String>, | ||||||
|  |     #[arg(long)] | ||||||
|  |     client: Option<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Args)] | #[derive(Args)] | ||||||
|  | @ -58,12 +63,29 @@ pub struct UpdateArgs { | ||||||
|     speakers: Option<bool>, |     speakers: Option<bool>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| impl FetchArgs { | impl FetchArgs { | ||||||
|     pub fn client(&self, connection: &mut Telnet) -> Result<Option<ResponseClient>, String> { |     pub fn want_channel(&self) -> bool { | ||||||
|         utils::find_client(connection, &self.client, self.strict_client) |         self.channel.is_some() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn want_client(&self) -> bool { | ||||||
|  |         self.client.is_some() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn channel(&self, connection: &mut Telnet) -> Result<Option<ResponseChannel>, 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<Option<ResponseClient>, String> { | ||||||
|  |         if let Some(client) = &self.client { | ||||||
|  |             utils::find_client(connection, client, self.strict_client) | ||||||
|  |         } else { | ||||||
|  |             Err("No client specified.".to_string()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl MoveArgs { | impl MoveArgs { | ||||||
|  |  | ||||||
|  | @ -57,8 +57,7 @@ fn read_response(connection: &mut Telnet, skip_ok: bool, mut buffer: String) -> | ||||||
|                 if skip_ok { |                 if skip_ok { | ||||||
|                     read_response(connection, skip_ok, buffer) |                     read_response(connection, skip_ok, buffer) | ||||||
|                 } else { |                 } else { | ||||||
|                     // empty Ok response
 |                     Ok(Response::Ok) | ||||||
|                     Ok(Response::Data(Vec::new())) |  | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 Err(format!("Received error response from Teamspeak: {} ({})", err.msg, err.id)) |                 Err(format!("Received error response from Teamspeak: {} ({})", err.msg, err.id)) | ||||||
|  |  | ||||||
							
								
								
									
										70
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										70
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -8,6 +8,29 @@ mod cli; | ||||||
| use std::process::exit; | use std::process::exit; | ||||||
| use telnet::Telnet; | use telnet::Telnet; | ||||||
| use crate::cli::Commands; | use crate::cli::Commands; | ||||||
|  | use crate::response_classes::{ResponseChannel, ResponseClient}; | ||||||
|  | 
 | ||||||
|  | fn channel_or_exit(channel_res: Result<Option<ResponseChannel>, 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<Option<ResponseClient>, 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() { | fn main() { | ||||||
| 
 | 
 | ||||||
|  | @ -55,17 +78,20 @@ fn main() { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Commands::Fetch(args) => { |         Commands::Fetch(args) => { | ||||||
|             let client = args.client(&mut connection).unwrap_or_else(|err| { |             if args.want_client() && args.want_channel() { | ||||||
|                 println!("Failed to find client for move: {}", err); |                 println!("Fetching both clients and channels is not supported."); | ||||||
|                 exit(1); |                 exit(1); | ||||||
|             }) |             } | ||||||
|                 .unwrap_or_else(|| { |             if !args.want_client() && !args.want_channel() { | ||||||
|                     println!("Failed to find client for move."); |                 println!("No clients or channels specified."); | ||||||
|                 exit(1); |                 exit(1); | ||||||
|                 }); |             } | ||||||
|  | 
 | ||||||
|  |             if args.want_client() { | ||||||
|  |                 let client = client_or_exit(args.client(&mut connection)); | ||||||
| 
 | 
 | ||||||
|                 match utils::fetch_client(&mut connection, &[client]) { |                 match utils::fetch_client(&mut connection, &[client]) { | ||||||
|                 Ok(resp) => println!("Successfully fetched client: {}", resp), |                     Ok(_) => println!("Successfully fetched client."), | ||||||
|                     Err(msg) => { |                     Err(msg) => { | ||||||
|                         println!("Failed to fetch client: {}", msg); |                         println!("Failed to fetch client: {}", msg); | ||||||
|                         exit(1); |                         exit(1); | ||||||
|  | @ -73,24 +99,22 @@ fn main() { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         Commands::Move(args) => { |             if args.want_channel() { | ||||||
|             let channel = args.channel(&mut connection).unwrap_or_else(|err| { |                 let channel = channel_or_exit(args.channel(&mut connection)); | ||||||
|                 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| { |                 match utils::fetch_channel(&mut connection, channel) { | ||||||
|                 println!("Failed to find client for move: {}", err); |                     Ok(_) => println!("Successfully fetched channel."), | ||||||
|  |                     Err(msg) => { | ||||||
|  |                         println!("Failed to fetch channel: {}", msg); | ||||||
|                         exit(1); |                         exit(1); | ||||||
|             }) |                     } | ||||||
|                 .unwrap_or_else(|| { |                 } | ||||||
|                     println!("Failed to find client for move."); |             } | ||||||
|                     exit(1); |         } | ||||||
|                 }); | 
 | ||||||
|  |         Commands::Move(args) => { | ||||||
|  |             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]) { |             match utils::move_client(&mut connection, &channel, &[client]) { | ||||||
|                 Ok(resp) => println!("Successfully moved client: {}", resp), |                 Ok(resp) => println!("Successfully moved client: {}", resp), | ||||||
|  |  | ||||||
|  | @ -18,21 +18,21 @@ pub fn parameter_find(params: &Vec<Parameter>, name: &str) -> Option<Parameter> | ||||||
| } | } | ||||||
| pub fn parameter_list_find(param_lists: &Vec<ParameterList>, name: &str, value: &str, strict: bool) -> Option<ParameterList> { | pub fn parameter_list_find(param_lists: &Vec<ParameterList>, name: &str, value: &str, strict: bool) -> Option<ParameterList> { | ||||||
|     for params in param_lists { |     for params in param_lists { | ||||||
|         for param in params { |         if params.iter().position(|param| param.is(name, value, strict)).is_some() { | ||||||
|             if param.name != name { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             if param.value != value && strict { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             if !param.value.contains(value) && !strict { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             return Some(params.clone()); |             return Some(params.clone()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     None |     None | ||||||
| } | } | ||||||
|  | pub fn parameter_list_find_all(param_lists: &Vec<ParameterList>, name: &str, value: &str, strict: bool) -> Vec<ParameterList> { | ||||||
|  |     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 { | pub fn parameter_parse(params_str: &str) -> ParameterList { | ||||||
|     let parts: Vec<&str> = params_str.split(' ').collect(); |     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 { |     pub fn to_i32(&self, default: i32) -> i32 { | ||||||
|         self.value.parse::<i32>().unwrap_or(default) |         self.value.parse::<i32>().unwrap_or(default) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ use std::fmt::{Debug, Display, Formatter}; | ||||||
| use crate::parameter::*; | use crate::parameter::*; | ||||||
| 
 | 
 | ||||||
| pub enum Response { | pub enum Response { | ||||||
|  |     Ok, | ||||||
|     Data(ParameterList), |     Data(ParameterList), | ||||||
|     DataList(Vec<ParameterList>), |     DataList(Vec<ParameterList>), | ||||||
| } | } | ||||||
|  | @ -36,6 +37,9 @@ impl TryFrom<String> for Response { | ||||||
| impl Debug for Response { | impl Debug for Response { | ||||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||||||
|            match self { |            match self { | ||||||
|  |                Response::Ok => { | ||||||
|  |                    write!(f, "Ok") | ||||||
|  |                } | ||||||
|                 Response::Data(params) => { |                 Response::Data(params) => { | ||||||
|                     write!(f, "Data:")?; |                     write!(f, "Data:")?; | ||||||
|                     write!(f, "{:?};", params)?; |                     write!(f, "{:?};", params)?; | ||||||
|  | @ -55,6 +59,9 @@ impl Debug for Response { | ||||||
| impl Display for Response { | impl Display for Response { | ||||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||||||
|         match self { |         match self { | ||||||
|  |             Response::Ok => { | ||||||
|  |                 write!(f, "Ok") | ||||||
|  |             } | ||||||
|             Response::Data(params) => { |             Response::Data(params) => { | ||||||
|                 for param in params { |                 for param in params { | ||||||
|                     write!(f, "{};", param)?; |                     write!(f, "{};", param)?; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | use std::fmt::{Display, Formatter}; | ||||||
| use crate::parameter::{parameter_find, ParameterList}; | use crate::parameter::{parameter_find, ParameterList}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
|  | @ -10,6 +11,12 @@ pub struct ResponseChannel { | ||||||
|     pub channel_flag_are_subscribed: bool, |     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<ParameterList> for ResponseChannel { | impl From<ParameterList> for ResponseChannel { | ||||||
|     fn from(value: ParameterList) -> Self { |     fn from(value: ParameterList) -> Self { | ||||||
|         ResponseChannel { |         ResponseChannel { | ||||||
|  | @ -83,6 +90,12 @@ pub struct ResponseClient { | ||||||
|     pub client_type: i32, |     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<ParameterList> for ResponseClient { | impl From<ParameterList> for ResponseClient { | ||||||
|     fn from(value: ParameterList) -> Self { |     fn from(value: ParameterList) -> Self { | ||||||
|         ResponseClient { |         ResponseClient { | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								src/utils.rs
									
										
									
									
									
								
							
							
						
						
									
										29
									
								
								src/utils.rs
									
										
									
									
									
								
							|  | @ -4,7 +4,7 @@ use std::time::Duration; | ||||||
| use telnet::Event::TimedOut; | use telnet::Event::TimedOut; | ||||||
| 
 | 
 | ||||||
| use crate::{commands, parameter}; | use crate::{commands, parameter}; | ||||||
| use crate::parameter::ParameterList; | use crate::parameter::{parameter_list_find_all, ParameterList}; | ||||||
| use crate::response::Response; | use crate::response::Response; | ||||||
| use crate::response_classes::{ResponseChannel, ResponseClient}; | 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<Vec<ResponseClient>, String> { | ||||||
|  |     match commands::clientlist(connection) { | ||||||
|  |         Ok(response) => { | ||||||
|  |             match response { | ||||||
|  |                 Response::DataList(parameter_lists) => { | ||||||
|  |                     let mut clients: Vec<ResponseClient> = 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<String, String> { | fn get_self_clid(connection: &mut Telnet) -> Result<String, String> { | ||||||
|     match commands::whoami(connection) { |     match commands::whoami(connection) { | ||||||
|         Ok(response) => { |         Ok(response) => { | ||||||
|  | @ -168,6 +186,15 @@ pub fn fetch_client(connection: &mut Telnet, clients: &[ResponseClient]) -> Resu | ||||||
|     commands::clientmove(connection, &cid, clid_list) |     commands::clientmove(connection, &cid, clid_list) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | pub fn fetch_channel(connection: &mut Telnet, channel: ResponseChannel) -> Result<Response, String> { | ||||||
|  |     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<Response, String> { | pub fn move_client(connection: &mut Telnet, channel: &ResponseChannel, clients: &[ResponseClient]) -> Result<Response, String> { | ||||||
|     let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect(); |     let clid_list: Vec<&i32> = clients.iter().map(|c| &c.clid).collect(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										28
									
								
								ts-control
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								ts-control
									
										
									
									
									
								
							|  | @ -1,6 +1,13 @@ | ||||||
| #!/usr/bin/env sh | #!/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() { | ts_control_move_self() { | ||||||
| 	channel=$(teamspeak-query-lib channels | $DMENU) | 	channel=$(teamspeak-query-lib channels | $DMENU) | ||||||
|  | @ -11,16 +18,23 @@ ts_control_move_self() { | ||||||
| 	return 0 | 	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 | case $action in | ||||||
| 	"move") | 	"move") | ||||||
| 		ts_control_move_self | 		ts_control_move_self | ||||||
| 		;; | 		;; | ||||||
| 	"fetch") | 	"fetch-client") | ||||||
| 		client=$(teamspeak-query-lib clients | $DMENU) | 	  ts_control_fetch client | ||||||
| 		if [ -z "$client" ]; then | 		;; | ||||||
| 			exit 1 | 	"fetch-channel") | ||||||
| 		fi | 	  ts_control_fetch channel | ||||||
| 		teamspeak-query-lib fetch --strict-client "$client" |  | ||||||
| 		;; | 		;; | ||||||
| 	"not away") | 	"not away") | ||||||
| 		teamspeak-query-lib move "Not Away From Keyboard" | 		teamspeak-query-lib move "Not Away From Keyboard" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue