2021-11-25 23:40:07 +01:00
|
|
|
use std::{io::Read, path::PathBuf};
|
|
|
|
|
2021-11-23 14:22:58 +01:00
|
|
|
use clap::Parser;
|
2021-07-31 17:16:00 +02:00
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
/// Bert
|
2021-11-23 14:22:58 +01:00
|
|
|
#[derive(Parser, Debug)]
|
2021-07-31 17:16:00 +02:00
|
|
|
#[clap(version, author, about)]
|
|
|
|
struct Opts {
|
|
|
|
/// Home Assistant host
|
|
|
|
#[clap(short, long)]
|
|
|
|
host: String,
|
|
|
|
|
|
|
|
/// Media player entity ID
|
|
|
|
#[clap(short, long)]
|
|
|
|
entity: String,
|
|
|
|
|
|
|
|
/// API token
|
|
|
|
#[clap(short, long)]
|
2021-11-25 23:40:07 +01:00
|
|
|
token: Option<String>,
|
|
|
|
|
|
|
|
/// File with the API token
|
|
|
|
#[clap(long)]
|
|
|
|
token_file: Option<PathBuf>,
|
2021-07-31 17:16:00 +02:00
|
|
|
|
|
|
|
/// Use HTTP instead of HTTPS
|
|
|
|
#[clap(short, long)]
|
|
|
|
insecure: bool,
|
|
|
|
|
|
|
|
#[clap(subcommand)]
|
|
|
|
cmd: Option<Command>,
|
|
|
|
}
|
|
|
|
|
2021-11-23 14:22:58 +01:00
|
|
|
#[derive(Parser, Debug, Clone, Copy)]
|
2021-07-31 17:16:00 +02:00
|
|
|
enum Command {
|
|
|
|
/// Toggle playback
|
|
|
|
PlayPause,
|
|
|
|
/// Raise volume
|
|
|
|
VolumeUp,
|
|
|
|
/// Lower volume
|
|
|
|
VolumeDown,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
let opts = Opts::parse();
|
|
|
|
|
2021-11-25 23:40:07 +01:00
|
|
|
let Opts {
|
|
|
|
host,
|
|
|
|
entity,
|
|
|
|
insecure,
|
|
|
|
cmd,
|
|
|
|
token,
|
|
|
|
token_file,
|
|
|
|
} = opts;
|
|
|
|
|
|
|
|
let token = if let Some(token) = token {
|
|
|
|
Some(token)
|
|
|
|
} else if let Some(token_file) = token_file {
|
|
|
|
if let Ok(mut file) = std::fs::File::open(token_file) {
|
|
|
|
let mut buf = String::new();
|
|
|
|
file.read_to_string(&mut buf).ok();
|
|
|
|
Some(buf)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2021-07-31 17:16:00 +02:00
|
|
|
} else {
|
2021-11-25 23:40:07 +01:00
|
|
|
eprintln!("No API token given. Use either --token or --token-file");
|
|
|
|
None
|
|
|
|
};
|
2021-07-31 17:16:00 +02:00
|
|
|
|
2021-11-25 23:40:07 +01:00
|
|
|
if let Some(token) = token {
|
|
|
|
if let Some(cmd) = cmd {
|
|
|
|
call_service(cmd, host, entity, insecure, token)?;
|
2021-07-31 17:16:00 +02:00
|
|
|
} else {
|
2021-11-25 23:40:07 +01:00
|
|
|
get_now_playing(host, entity, insecure, token)?;
|
2021-07-31 17:16:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// println!("{:#?}", response);
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-11-25 23:40:07 +01:00
|
|
|
fn get_now_playing(
|
|
|
|
host: String,
|
|
|
|
entity: String,
|
|
|
|
insecure: bool,
|
|
|
|
token: String,
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
let url = format!(
|
|
|
|
"{}://{}/api/states/{}",
|
|
|
|
if insecure { "http" } else { "https" },
|
|
|
|
host,
|
|
|
|
entity
|
|
|
|
);
|
|
|
|
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
|
|
let response = client
|
|
|
|
.get(url)
|
|
|
|
.bearer_auth(token)
|
|
|
|
.header("Accept", "application/json")
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
.send()?
|
|
|
|
.json::<serde_json::Value>()?;
|
|
|
|
|
|
|
|
if response["state"] == "playing" {
|
|
|
|
let attributes = &response["attributes"];
|
|
|
|
let maybe_channel = attributes["media_channel"].as_str();
|
|
|
|
let now_playing = if let Some(channel) = maybe_channel {
|
|
|
|
format!(
|
|
|
|
"[{}] {} - {}",
|
|
|
|
channel,
|
|
|
|
attributes["media_artist"]
|
|
|
|
.as_str()
|
|
|
|
.map_or("No artist", |artist| { artist }),
|
|
|
|
attributes["media_title"]
|
|
|
|
.as_str()
|
|
|
|
.map_or("No title", |title| { title }),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"{} - {}",
|
|
|
|
attributes["media_artist"]
|
|
|
|
.as_str()
|
|
|
|
.map_or("No artist", |artist| artist),
|
|
|
|
attributes["media_title"]
|
|
|
|
.as_str()
|
|
|
|
.map_or("No title", |title| title),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
println!("{}", now_playing);
|
|
|
|
} else {
|
|
|
|
println!(
|
|
|
|
"Sonos {}",
|
|
|
|
response["state"]
|
|
|
|
.as_str()
|
|
|
|
.map_or("state unknown", |state| state)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-07-31 17:16:00 +02:00
|
|
|
|
2021-11-25 23:40:07 +01:00
|
|
|
fn call_service(
|
|
|
|
command: Command,
|
|
|
|
host: String,
|
|
|
|
entity: String,
|
|
|
|
insecure: bool,
|
|
|
|
token: String,
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2021-07-31 17:16:00 +02:00
|
|
|
let cmd = match command {
|
|
|
|
Command::PlayPause => "media_play_pause",
|
|
|
|
Command::VolumeUp => "volume_up",
|
|
|
|
Command::VolumeDown => "volume_down",
|
|
|
|
};
|
|
|
|
|
|
|
|
let url = format!(
|
|
|
|
"{}://{}/api/services/media_player/{}",
|
2021-11-25 23:40:07 +01:00
|
|
|
if insecure { "http" } else { "https" },
|
|
|
|
host,
|
2021-07-31 17:16:00 +02:00
|
|
|
cmd
|
|
|
|
);
|
|
|
|
|
2021-11-25 23:40:07 +01:00
|
|
|
let body = json!({ "entity_id": entity }).to_string();
|
2021-07-31 17:16:00 +02:00
|
|
|
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
|
|
let response = client
|
|
|
|
.post(url)
|
|
|
|
.body(body)
|
2021-11-25 23:40:07 +01:00
|
|
|
.bearer_auth(token)
|
2021-07-31 17:16:00 +02:00
|
|
|
.header("Accept", "application/json")
|
|
|
|
.header("Content-Type", "application/json")
|
|
|
|
.send()?
|
|
|
|
.json::<serde_json::Value>()?;
|
|
|
|
|
|
|
|
println!("{:#?}", response);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|