This commit is contained in:
zack 2025-07-27 15:55:35 -04:00
parent f67be1d367
commit c12b4787ca
Signed by: zoey
GPG key ID: 81FB9FECDD6A33E2
3 changed files with 258 additions and 183 deletions

73
src/actions.rs Normal file
View file

@ -0,0 +1,73 @@
use serde::Serialize;
use crate::structs::{IsPlayingResponse, NowPlayingResponse};
pub fn get_now_playing() -> Result<NowPlayingResponse, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/now-playing";
// This call blocks until the request is complete.
let response = reqwest::blocking::get(URL)?;
let data: NowPlayingResponse = response.json()?;
Ok(data)
}
pub fn toggle_playpause() -> Result<serde_json::Value, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/playpause";
let client = reqwest::blocking::Client::new();
// This call blocks until the request is complete.
let response = client.post(URL).send()?;
let data = response.json()?;
Ok(data)
}
pub fn skip() -> Result<serde_json::Value, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/next";
let client = reqwest::blocking::Client::new();
// This call blocks until the request is complete.
let response = client.post(URL).send()?;
let data = response.json()?;
Ok(data)
}
pub fn like() -> Result<serde_json::Value, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/set-rating";
let client = reqwest::blocking::Client::new();
#[derive(Serialize)]
struct RatingBody {
rating: i32,
}
// This call blocks until the request is complete.
let response = client
.post(URL)
.header("Content-Type", "application/json")
.body(serde_json::to_string(&RatingBody { rating: 1 }).unwrap())
.send()?;
let data = response.json()?;
Ok(data)
}
pub fn get_is_playing() -> Result<IsPlayingResponse, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/is-playing";
// This call blocks until the request is complete.
let response = reqwest::blocking::get(URL)?;
let data: IsPlayingResponse = response.json()?;
Ok(data)
}

View file

@ -6,168 +6,15 @@ use std::{
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use crossbeam_channel::unbounded; use crossbeam_channel::unbounded;
use rust_socketio::{ClientBuilder, Payload}; use rust_socketio::{ClientBuilder, Payload};
use serde::{Deserialize, Serialize};
use tracing::{error, info}; use tracing::{error, info};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] use crate::{
pub struct PlaybackUpdate { actions::{get_is_playing, get_now_playing, like, skip, toggle_playpause},
pub data: Data, structs::{PlaybackUpdate, StatusUpdate},
#[serde(rename = "type")] };
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] mod actions;
pub struct Data { mod structs;
#[serde(rename = "currentPlaybackDuration")]
pub current_playback_duration: Option<f64>,
#[serde(rename = "currentPlaybackTime")]
pub current_playback_time: Option<f64>,
#[serde(rename = "currentPlaybackTimeRemaining")]
pub current_playback_time_remaining: Option<f64>,
#[serde(rename = "isPlaying")]
pub is_playing: Option<bool>,
#[serde(rename = "albumName")]
pub album_name: Option<String>,
#[serde(rename = "artistName")]
pub artist_name: Option<String>,
pub artwork: Option<Artwork>,
#[serde(rename = "audioLocale")]
pub audio_locale: Option<String>,
#[serde(rename = "audioTraits")]
pub audio_traits: Option<Vec<String>>,
#[serde(rename = "composerName")]
pub composer_name: Option<String>,
#[serde(rename = "discNumber")]
pub disc_number: Option<i64>,
#[serde(rename = "durationInMillis")]
pub duration_in_millis: Option<i64>,
#[serde(rename = "genreNames")]
#[serde(default)]
pub genre_names: Vec<String>,
#[serde(rename = "hasLyrics")]
pub has_lyrics: Option<bool>,
#[serde(rename = "hasTimeSyncedLyrics")]
pub has_time_synced_lyrics: Option<bool>,
#[serde(rename = "isAppleDigitalMaster")]
pub is_apple_digital_master: Option<bool>,
#[serde(rename = "isMasteredForItunes")]
pub is_mastered_for_itunes: Option<bool>,
#[serde(rename = "isVocalAttenuationAllowed")]
pub is_vocal_attenuation_allowed: Option<bool>,
pub isrc: Option<String>,
pub name: Option<String>,
#[serde(rename = "playParams")]
pub play_params: Option<PlayParams>,
#[serde(default)]
pub previews: Vec<Preview>,
#[serde(rename = "releaseDate")]
pub release_date: Option<String>,
#[serde(rename = "remainingTime")]
pub remaining_time: Option<f64>,
#[serde(rename = "trackNumber")]
pub track_number: Option<i64>,
pub url: Option<String>,
pub attributes: Option<Attributes>,
pub state: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Artwork {
pub height: i64,
pub url: String,
pub width: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlayParams {
pub id: String,
pub kind: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Preview {
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attributes {
#[serde(rename = "albumName")]
pub album_name: String,
#[serde(rename = "artistName")]
pub artist_name: String,
pub artwork: Artwork2,
#[serde(rename = "audioLocale")]
pub audio_locale: String,
#[serde(rename = "audioTraits")]
pub audio_traits: Vec<String>,
#[serde(rename = "composerName")]
pub composer_name: String,
#[serde(rename = "currentPlaybackTime")]
pub current_playback_time: f64,
#[serde(rename = "discNumber")]
pub disc_number: i64,
#[serde(rename = "durationInMillis")]
pub duration_in_millis: i64,
#[serde(rename = "genreNames")]
pub genre_names: Vec<String>,
#[serde(rename = "hasLyrics")]
pub has_lyrics: bool,
#[serde(rename = "hasTimeSyncedLyrics")]
pub has_time_synced_lyrics: bool,
#[serde(rename = "isAppleDigitalMaster")]
pub is_apple_digital_master: bool,
#[serde(rename = "isMasteredForItunes")]
pub is_mastered_for_itunes: bool,
#[serde(rename = "isVocalAttenuationAllowed")]
pub is_vocal_attenuation_allowed: bool,
pub isrc: String,
pub name: String,
#[serde(rename = "playParams")]
pub play_params: PlayParams2,
pub previews: Vec<Preview2>,
#[serde(rename = "releaseDate")]
pub release_date: String,
#[serde(rename = "remainingTime")]
pub remaining_time: f64,
#[serde(rename = "trackNumber")]
pub track_number: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Artwork2 {
pub height: i64,
pub url: String,
pub width: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlayParams2 {
pub id: String,
pub kind: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Preview2 {
pub url: String,
}
#[derive(Debug, Deserialize)]
struct NowPlayingResponse {
info: Data,
}
#[derive(Debug, Deserialize)]
struct IsPlayingResponse {
is_playing: bool,
}
#[derive(Clone, Serialize, Deserialize)]
struct StatusUpdate {
text: String,
tooltip: String,
class: Vec<String>,
}
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -178,6 +25,13 @@ struct Cli {
#[derive(Subcommand, Clone, Debug)] #[derive(Subcommand, Clone, Debug)]
enum Commands { enum Commands {
/// Like the current song.
Like,
/// Skip the current song.
Skip,
/// Toggle play/pause.
PlayPause,
/// Monitor song changes from Cider /// Monitor song changes from Cider
Monitor, Monitor,
} }
@ -261,35 +115,22 @@ fn setup_ctrlc_handler() -> crossbeam_channel::Receiver<()> {
receiver receiver
} }
fn get_now_playing() -> Result<NowPlayingResponse, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/now-playing";
// This call blocks until the request is complete.
let response = reqwest::blocking::get(URL)?;
let data: NowPlayingResponse = response.json()?;
Ok(data)
}
fn get_is_playing() -> Result<IsPlayingResponse, reqwest::Error> {
const URL: &str = "http://localhost:10767/api/v1/playback/is-playing";
// This call blocks until the request is complete.
let response = reqwest::blocking::get(URL)?;
let data: IsPlayingResponse = response.json()?;
Ok(data)
}
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install()?; // color_eyre::install()?;
tracing_subscriber::fmt().pretty().init(); // tracing_subscriber::fmt().pretty().init();
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Some(Commands::Like) => {
like()?;
}
Some(Commands::Skip) => {
skip()?;
}
Some(Commands::PlayPause) => {
toggle_playpause()?;
}
Some(Commands::Monitor) => { Some(Commands::Monitor) => {
let ctrlc_receiver = setup_ctrlc_handler(); let ctrlc_receiver = setup_ctrlc_handler();

161
src/structs.rs Normal file
View file

@ -0,0 +1,161 @@
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlaybackUpdate {
pub data: Data,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Data {
#[serde(rename = "currentPlaybackDuration")]
pub current_playback_duration: Option<f64>,
#[serde(rename = "currentPlaybackTime")]
pub current_playback_time: Option<f64>,
#[serde(rename = "currentPlaybackTimeRemaining")]
pub current_playback_time_remaining: Option<f64>,
#[serde(rename = "isPlaying")]
pub is_playing: Option<bool>,
#[serde(rename = "albumName")]
pub album_name: Option<String>,
#[serde(rename = "artistName")]
pub artist_name: Option<String>,
pub artwork: Option<Artwork>,
#[serde(rename = "audioLocale")]
pub audio_locale: Option<String>,
#[serde(rename = "audioTraits")]
pub audio_traits: Option<Vec<String>>,
#[serde(rename = "composerName")]
pub composer_name: Option<String>,
#[serde(rename = "discNumber")]
pub disc_number: Option<i64>,
#[serde(rename = "durationInMillis")]
pub duration_in_millis: Option<i64>,
#[serde(rename = "genreNames")]
#[serde(default)]
pub genre_names: Vec<String>,
#[serde(rename = "hasLyrics")]
pub has_lyrics: Option<bool>,
#[serde(rename = "hasTimeSyncedLyrics")]
pub has_time_synced_lyrics: Option<bool>,
#[serde(rename = "isAppleDigitalMaster")]
pub is_apple_digital_master: Option<bool>,
#[serde(rename = "isMasteredForItunes")]
pub is_mastered_for_itunes: Option<bool>,
#[serde(rename = "isVocalAttenuationAllowed")]
pub is_vocal_attenuation_allowed: Option<bool>,
pub isrc: Option<String>,
pub name: Option<String>,
#[serde(rename = "playParams")]
pub play_params: Option<PlayParams>,
#[serde(default)]
pub previews: Vec<Preview>,
#[serde(rename = "releaseDate")]
pub release_date: Option<String>,
#[serde(rename = "remainingTime")]
pub remaining_time: Option<f64>,
#[serde(rename = "trackNumber")]
pub track_number: Option<i64>,
pub url: Option<String>,
pub attributes: Option<Attributes>,
pub state: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Artwork {
pub height: i64,
pub url: String,
pub width: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlayParams {
pub id: String,
pub kind: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Preview {
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attributes {
#[serde(rename = "albumName")]
pub album_name: String,
#[serde(rename = "artistName")]
pub artist_name: String,
pub artwork: Artwork2,
#[serde(rename = "audioLocale")]
pub audio_locale: String,
#[serde(rename = "audioTraits")]
pub audio_traits: Vec<String>,
#[serde(rename = "composerName")]
pub composer_name: String,
#[serde(rename = "currentPlaybackTime")]
pub current_playback_time: f64,
#[serde(rename = "discNumber")]
pub disc_number: i64,
#[serde(rename = "durationInMillis")]
pub duration_in_millis: i64,
#[serde(rename = "genreNames")]
pub genre_names: Vec<String>,
#[serde(rename = "hasLyrics")]
pub has_lyrics: bool,
#[serde(rename = "hasTimeSyncedLyrics")]
pub has_time_synced_lyrics: bool,
#[serde(rename = "isAppleDigitalMaster")]
pub is_apple_digital_master: bool,
#[serde(rename = "isMasteredForItunes")]
pub is_mastered_for_itunes: bool,
#[serde(rename = "isVocalAttenuationAllowed")]
pub is_vocal_attenuation_allowed: bool,
pub isrc: String,
pub name: String,
#[serde(rename = "playParams")]
pub play_params: PlayParams2,
pub previews: Vec<Preview2>,
#[serde(rename = "releaseDate")]
pub release_date: String,
#[serde(rename = "remainingTime")]
pub remaining_time: f64,
#[serde(rename = "trackNumber")]
pub track_number: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Artwork2 {
pub height: i64,
pub url: String,
pub width: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PlayParams2 {
pub id: String,
pub kind: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Preview2 {
pub url: String,
}
#[derive(Debug, Deserialize)]
pub struct NowPlayingResponse {
pub info: Data,
}
#[derive(Debug, Deserialize)]
pub struct IsPlayingResponse {
pub is_playing: bool,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct StatusUpdate {
pub text: String,
pub tooltip: String,
pub class: Vec<String>,
}