Add fetch-channel command

This commit is contained in:
Tobias Reisinger 2023-11-17 17:32:15 +01:00
parent 970c1ee2c2
commit 244a7073fe
Signed by: serguzim
GPG key ID: 13AD60C237A28DFE
9 changed files with 183 additions and 54 deletions

8
Makefile Normal file
View file

@ -0,0 +1,8 @@
.PHONY: run
build:
@cargo build
run: build
@PATH=$(PWD)/target/debug:$(PATH) ./ts-control

View file

@ -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 {

View file

@ -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))

View file

@ -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,42 +78,43 @@ 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);
}); }
match utils::fetch_client(&mut connection, &[client]) { if args.want_client() {
Ok(resp) => println!("Successfully fetched client: {}", resp), let client = client_or_exit(args.client(&mut connection));
Err(msg) => {
println!("Failed to fetch client: {}", msg); match utils::fetch_client(&mut connection, &[client]) {
exit(1); 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) => { Commands::Move(args) => {
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); let client = client_or_exit(args.client(&mut connection));
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);
});
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),

View file

@ -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)
} }

View file

@ -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)?;
@ -48,13 +52,16 @@ impl Debug for Response {
} }
Ok(()) Ok(())
} }
} }
} }
} }
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)?;

View file

@ -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 {

View file

@ -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(&parameter_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();

View file

@ -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"