Mercurial > beefweb_mpris
view src/beefweb.rs @ 6:482bd968725f default tip
beefweb: remove unnecessary (de)serialize derives
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 06 Apr 2026 09:54:05 -0400 |
| parents | 26f695129c86 |
| children |
line wrap: on
line source
/* * Beefweb <-> mpris "compatibility" layer. * * Copyright (C) 2026 Paper * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see * <https://www.gnu.org/licenses/>. */ /* Fuck off, I don't care. */ #![allow(nonstandard_style)] #[derive(serde::Deserialize, Debug)] pub struct PlayerInfo { pub name: String, pub title: String, pub version: String, // fuck camel case #[serde(rename = "pluginVersion")] pub plugin_version: String, } #[derive(serde::Deserialize, Debug)] pub struct ActiveItemInfo { #[serde(rename = "playlistId")] pub playlist_id: String, #[serde(rename = "playlistIndex")] pub playlist_index: i64, pub index: i64, pub position: f64, pub duration: f64, pub columns: Vec<String>, } #[derive(serde::Deserialize, Debug)] pub enum VolumeType { #[serde(rename = "db")] DB, #[serde(rename = "linear")] LINEAR, #[serde(rename = "upDown")] UPDOWN, /* ??? */ } #[derive(serde::Deserialize, Debug)] pub struct VolumeInfo { pub r#type: VolumeType, pub min: f64, pub max: f64, pub value: f64, #[serde(rename = "isMuted")] pub muted: bool, } #[derive(serde::Deserialize, Debug)] pub struct ApiPermissions { #[serde(rename = "changePlaylists")] pub change_playlists: bool, #[serde(rename = "changeOutput")] pub change_output: bool, #[serde(rename = "changeClientConfig")] pub change_client_config: bool, } #[derive(serde::Deserialize, Debug)] pub enum PlaybackState { #[serde(rename = "stopped")] STOPPED, #[serde(rename = "playing")] PLAYING, #[serde(rename = "paused")] PAUSED, } /* fb2k playback order */ #[derive(Debug)] pub enum PlaybackOrder { DEFAULT, REPEAT_PLAYLIST, REPEAT_TRACK, RANDOM, SHUFFLE_TRACKS, SHUFFLE_ALBUMS, SHUFFLE_FOLDERS, } #[derive(serde::Deserialize, Debug)] #[serde(tag = "type")] pub enum Options { #[serde(rename = "enum")] Enumeration { #[serde(rename = "enumNames")] enum_names: Vec<String>, id: String, name: String, value: i64, }, #[serde(rename = "bool")] Boolean { id: String, name: String, value: bool, }, } #[derive(serde::Deserialize, Debug)] pub struct Player { pub info: PlayerInfo, #[serde(rename = "activeItem")] pub active_item: ActiveItemInfo, /* XXX actually an enum */ #[serde(rename = "playbackState")] pub playback_state: PlaybackState, pub volume: VolumeInfo, pub permissions: ApiPermissions, options: Option<Vec<Options>>, /* these two fields are deprecated, * replaced with "options" array */ #[serde(skip_serializing_if = "Option::is_none")] playbackMode: Option<i64>, #[serde(skip_serializing_if = "Option::is_none")] playbackModes: Option<Vec<String>>, } /* {"player": {struct Player}} */ #[derive(serde::Deserialize, Debug)] struct PlayerHelper { player: Player, } #[derive(serde::Serialize, Debug)] #[serde(untagged)] enum SetPlayerOption { Integer { id: String, value: i64 }, Boolean { id: String, value: bool }, } #[derive(serde::Serialize, Debug)] #[serde_with::skip_serializing_none] struct SetPlayer { /* dB */ volume: Option<f64>, /* ??? */ #[serde(rename = "relativeVolume")] relative_volume: Option<f64>, #[serde(rename = "isMuted")] muted: Option<bool>, position: Option<f64>, #[serde(rename = "relativePosition")] relative_position: Option<f64>, /* teehee */ options: Option<Vec<SetPlayerOption>>, } #[derive(serde::Deserialize, Debug)] pub struct PlaylistItem { pub columns: Vec<String> } #[derive(serde::Deserialize, Debug)] pub struct PlaylistItems { pub offset: i64, #[serde(rename = "totalCount")] pub total_count: i64, pub items: Vec<PlaylistItem>, } #[derive(serde::Deserialize, Debug)] struct PlaylistItemsHelper { #[serde(rename = "playlistItems")] playlist_items: PlaylistItems, } #[derive(serde::Deserialize, Debug)] pub struct Playlist { pub id: String, pub index: i64, pub title: String, #[serde(rename = "isCurrent")] pub current: bool, #[serde(rename = "itemCount")] pub item_count: i64, #[serde(rename = "totalTime")] pub total_time: f64, } #[derive(serde::Deserialize, Debug)] pub struct Playlists { pub playlists: Vec<Playlist>, } /* --- NOW, THE ACTUAL BEEFWEB THING */ pub struct Beefweb { client: reqwest::Client, base_url: String, } impl Beefweb { pub fn new(base: &str) -> Beefweb { return Beefweb { client: reqwest::Client::new(), base_url: base.to_string(), }; } pub async fn player(&self) -> Result<Player, anyhow::Error> { return Ok(self.client .get(format!("{}/player", self.base_url)) .send() .await? .json::<PlayerHelper>() .await? .player); } fn playback_order_lookup(playback_order: &str) -> Option<PlaybackOrder> { return match playback_order { "Default" => return Some(PlaybackOrder::DEFAULT), "Repeat (playlist)" => return Some(PlaybackOrder::REPEAT_PLAYLIST), "Repeat (track)" => return Some(PlaybackOrder::REPEAT_TRACK), "Random" => return Some(PlaybackOrder::RANDOM), "Shuffle (tracks)" => return Some(PlaybackOrder::SHUFFLE_TRACKS), "Shuffle (albums)" => return Some(PlaybackOrder::SHUFFLE_ALBUMS), "Shuffle (folders)" => return Some(PlaybackOrder::SHUFFLE_FOLDERS), _ => None, }; } fn playback_order_cruft(v: &Vec<String>, s: i64) -> Option<PlaybackOrder> { let x = v.get(s as usize); match x { Some(y) => return Self::playback_order_lookup(y), _ => (), }; return None; } pub async fn playback_order(&self) -> Result<PlaybackOrder, anyhow::Error> { let p = self.player().await?; match p.options { Some(x) => { for i in x { match i { Options::Enumeration { enum_names: e, id: id, name: _, value: v } => { match id.as_str() { "playbackOrder" => { match Self::playback_order_cruft(&e, v) { Some(z) => return Ok(z), _ => (), }; }, _ => (), }; }, _ => (), }; } }, _ => (), } match p.playbackModes { Some(x) => { match p.playbackMode { Some(y) => { match Self::playback_order_cruft(&x, y) { Some(v) => return Ok(v), _ => (), } }, _ => (), }; } _ => (), } return Err(anyhow::anyhow!("Unknown or invalid playback order?")); } pub async fn volume(&self) -> Result<VolumeInfo, anyhow::Error> { return Ok(self.player().await?.volume); } pub async fn position(&self) -> Result<f64, anyhow::Error> { return Ok(self.player().await?.active_item.position); } /* XXX might be able to use macros for this? idk --paper */ async fn play_pause_stop(&self, x: &str) -> Result<(), anyhow::Error> { self.client .post(format!("{}/player/{}", self.base_url, x)) .send() .await?; return Ok(()); } pub async fn play(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("play").await; } pub async fn pause(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("pause").await; } pub async fn stop(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("stop").await; } pub async fn play_pause(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("play-pause").await; } pub async fn next(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("next").await; } pub async fn previous(&self) -> Result<(), anyhow::Error> { return self.play_pause_stop("previous").await; } /* XXX what should this return? */ async fn set_player(&self, p: &SetPlayer) -> Result<(), anyhow::Error> { self.client .post(format!("{}/player", self.base_url)) .json::<SetPlayer>(p) .send() .await?; return Ok(()); } pub async fn set_volume(&self, volume: f64) -> Result<(), anyhow::Error> { return self.set_player(&SetPlayer { volume: Some(volume), relative_volume: None, muted: None, position: None, relative_position: None, options: None, }).await; } pub async fn set_position(&self, position: f64) -> Result<(), anyhow::Error> { return self.set_player(&SetPlayer { volume: None, relative_volume: None, muted: None, position: Some(position), relative_position: None, options: None, }).await; } pub async fn seek(&self, position: f64) -> Result<(), anyhow::Error> { return self.set_player(&SetPlayer { volume: None, relative_volume: None, muted: None, position: None, relative_position: Some(position), options: None, }).await; } /* TODO -- finish this; need to link between string and enum pub async fn set_playback_order(&self, ) -> Result<(), anyhow::Error> { let p = self.set_player(&SetPlayer { volume: None, relative_volume: None, muted: None, position: None, relative_position: None, options: }) } */ pub async fn playlist_items(&self, playlist_id: &str, offset: i64, count: i64, columns: &Vec<&str>) -> Result<PlaylistItems, anyhow::Error> { return Ok(self.client .get(format!("{}/playlists/{}/items/{}:{}", self.base_url, playlist_id, offset, count)) .query(&[("columns", columns.join(","))]) .send() .await? .json::<PlaylistItemsHelper>() .await? .playlist_items); } pub async fn playlist_item(&self, playlist_id: &str, index: i64, columns: &Vec<&str>) -> Result<PlaylistItems, anyhow::Error> { return self.playlist_items(playlist_id, index, 1, columns).await; } pub async fn playlists(&self) -> Result<Vec<Playlist>, anyhow::Error> { return Ok(self.client .get(format!("{}/playlists", self.base_url)) .send() .await? .json::<Playlists>() .await? .playlists); } pub async fn artwork(&self, playlist_id: &str, index: i64) -> Result<bytes::Bytes, anyhow::Error> { return Ok(self.client .get(format!("{}/artwork/{}/{}", self.base_url, playlist_id, index)) .send() .await? .bytes() .await?); } }
