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))
|
||||||
|
|
82
src/main.rs
82
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,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),
|
||||||
|
|
|
@ -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)?;
|
||||||
|
@ -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)?;
|
||||||
|
|
|
@ -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…
Reference in a new issue