diff --git a/Cargo.toml b/Cargo.toml index 7b13dde..c5980d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radiobrowser" -version = "0.2.0" +version = "0.3.0" authors = ["segler_alex "] edition = "2021" license = "MIT" @@ -16,15 +16,19 @@ async-std = { version = "1.11.0", features = ["attributes", "tokio1"] } async-std-resolver = "0.21.2" chrono = { version = "0.4.19", features = ["serde"] } futures = { version = "0.3.21" } -log = { version = "0.4.16" } +log = { version = "0.4.17" } rand = { version = "0.8.5" } reqwest = { version = "0.11.10", features = ["json"] } -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.137", features = ["derive"] } [features] -default = ["blocking"] +default = [] blocking = [] +[[bin]] +name = "test" +required-features = ["blocking"] + [badges] # The `maintenance` table indicates the status of the maintenance of # the crate. This may be used by a registry, but is currently not diff --git a/README.md b/README.md index c59aee6..6de5f9f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,45 @@ # Radiobrowser Lib Rust Client library for radio-browser.info and other radio-browser-rust servers -## Getting started +## Features +- [x] Async / Blocking API +- [x] Clean query api with builder pattern +- [x] Countries, languages, tags, stations, serverconfig +- [x] Server statistics +- [x] Station actions: click, vote +- [ ] Add streams + +## Getting started (Blocking) +### Example: +It needs to have the feature "blocking" enabled. +Cargo.toml entry: +```toml +radiobrowser = { version = "*", features = ["blocking"] } +``` +```rust +use radiobrowser::blocking::RadioBrowserAPI; +use std::error::Error; + +fn main() -> Result<(), Box> { + let api = RadioBrowserAPI::new()?; + let servers = RadioBrowserAPI::get_servers()?; + println!("Servers: {:?}", servers); + let status = RadioBrowserAPI::get_server_status()?; + println!("Status: {:?}", status); + let countries = api.get_countries().send()?; + println!("Countries: {:?}", countries); + let stations = api.get_stations().name("jazz").send()?; + println!("Stations: {:?}", stations); + Ok(()) +} +``` + +## Getting started (Async) Cargo.toml entry ```toml radiobrowser = "*" ``` -Example: +### Example: ```rust use radiobrowser::RadioBrowserAPI; use radiobrowser::StationOrder; diff --git a/src/api.rs b/src/api.rs index 5660a56..197c1da 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,3 +1,6 @@ +use crate::ApiStationClickResult; +use crate::ApiStationVoteResult; +use crate::ApiStatus; use crate::external::post_api; use crate::ApiConfig; use crate::CountrySearchBuilder; @@ -18,6 +21,21 @@ use async_std_resolver::proto::rr::RecordType; use async_std_resolver::proto::xfer::DnsRequestOptions; use async_std_resolver::{config, resolver}; + +/// RadioBrowser client for async communication +/// +/// It uses crate:async_std +/// +/// Example +/// ```rust +/// use std::error::Error; +/// use radiobrowser::RadioBrowserAPI; +/// #[async_std::main] +/// async fn main() -> Result<(), Box> { +/// let mut api = RadioBrowserAPI::new().await?; +/// Ok(()) +/// } +/// ``` #[derive(Clone, Debug)] pub struct RadioBrowserAPI { servers: Vec, @@ -25,6 +43,9 @@ pub struct RadioBrowserAPI { } impl RadioBrowserAPI { + /// Create a new instance of a radiobrowser api client. + /// It will fetch a list of radiobrowser server with get_servers() + /// and save it internally. pub async fn new() -> Result> { Ok(RadioBrowserAPI { servers: RadioBrowserAPI::get_servers().await?, @@ -54,6 +75,20 @@ impl RadioBrowserAPI { Ok(self.post_api("/json/config").await?) } + pub async fn get_server_status(&mut self) -> Result> { + Ok(self.post_api("/json/stats").await?) + } + + /// Add a click to a station found by stationuuid + pub async fn station_click>(&mut self, stationuuid: P) -> Result> { + Ok(self.post_api(format!("/json/url/{}",stationuuid.as_ref())).await?) + } + + /// Add a vote to a station found by a stationuuid + pub async fn station_vote>(&mut self, stationuuid: P) -> Result> { + Ok(self.post_api(format!("/json/vote/{}",stationuuid.as_ref())).await?) + } + pub fn get_stations(&self) -> StationSearchBuilder { StationSearchBuilder::new(self.clone()) } diff --git a/src/bin/test-async.rs b/src/bin/test-async.rs new file mode 100644 index 0000000..2346b93 --- /dev/null +++ b/src/bin/test-async.rs @@ -0,0 +1,38 @@ +use futures::join; +use radiobrowser::RadioBrowserAPI; +use radiobrowser::StationOrder; +use std::error::Error; + +#[async_std::main] +async fn main() -> Result<(), Box> { + let mut api = RadioBrowserAPI::new().await?; + let countries = api.get_countries().send(); + let languages = api.get_languages().send(); + let tags = api.get_tags().filter("jazz").send(); + let stations = api + .get_stations() + .name("jazz") + .reverse(true) + .order(StationOrder::Clickcount) + .send(); + let mut api2 = api.clone(); + let config = api.get_server_config(); + let stats = api2.get_server_status(); + let (stations, config, countries, languages, tags, stats) = join!(stations, config, countries, languages, tags, stats); + + println!("Config: {:#?}", config?); + println!("Status: {:#?}", stats?); + println!("Countries found: {}", countries?.len()); + println!("Languages found: {}", languages?.len()); + let tags = tags?; + println!("Tags found: {}", tags.len()); + println!("{:?}", tags); + let stations = stations?; + println!("Stations found: {}", stations.len()); + + let vote_result = api.station_vote(&stations[0].stationuuid).await?; + println!("Stations voted result: {:?}", vote_result); + let click_result = api.station_click(&stations[0].stationuuid).await?; + println!("Stations clicked result: {:?}", click_result); + Ok(()) +} diff --git a/src/bin/test.rs b/src/bin/test.rs index 2cb3807..8ab82f1 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -1,45 +1,21 @@ -/* -use futures::join; -use radiobrowser::RadioBrowserAPI; -use radiobrowser::StationOrder; -use std::error::Error; - -#[async_std::main] -async fn main() -> Result<(), Box> { - let mut api = RadioBrowserAPI::new().await?; - let countries = api.get_countries().send(); - let languages = api.get_languages().send(); - let tags = api.get_tags().filter("jazz").send(); - let stations = api - .get_stations() - .name("jazz") - .reverse(true) - .order(StationOrder::Clickcount) - .send(); - let config = api.get_server_config(); - let (stations, config, countries, languages, tags) = join!(stations, config, countries, languages, tags); - - println!("Config: {:#?}", config?); - println!("Countries found: {}", countries?.len()); - println!("Languages found: {}", languages?.len()); - let tags = tags?; - println!("Tags found: {}", tags.len()); - println!("{:?}", tags); - println!("Stations found: {}", stations?.len()); - Ok(()) -} -*/ - use radiobrowser::blocking::RadioBrowserAPI; use std::error::Error; fn main() -> Result<(), Box> { - let api = RadioBrowserAPI::new()?; + let mut api = RadioBrowserAPI::new()?; let servers = RadioBrowserAPI::get_servers()?; println!("Servers: {:?}", servers); + let status = api.get_server_status()?; + println!("Status: {:#?}", status); + let config = api.get_server_config()?; + println!("Config: {:#?}", config); let countries = api.get_countries().send()?; - println!("Countries: {:?}", countries); + println!("Countries: {:?}", countries.len()); let stations = api.get_stations().name("jazz").send()?; - println!("Stations: {:?}", stations); + println!("Stations with name containing 'jazz': {:?}", stations.len()); + let vote_result = api.station_vote(&stations[0].stationuuid)?; + println!("Stations voted result: {:?}", vote_result); + let click_result = api.station_click(&stations[0].stationuuid)?; + println!("Stations clicked result: {:?}", click_result); Ok(()) -} \ No newline at end of file +} diff --git a/src/blocking/api.rs b/src/blocking/api.rs index 4f5355d..00fee64 100644 --- a/src/blocking/api.rs +++ b/src/blocking/api.rs @@ -2,6 +2,10 @@ use crate::blocking::stationsearchbuilder::StationSearchBuilder; use crate::blocking::CountrySearchBuilder; use crate::blocking::LanguageSearchBuilder; use crate::blocking::TagSearchBuilder; +use crate::ApiStationClickResult; +use crate::ApiStationVoteResult; +use crate::ApiStatus; +use crate::ApiConfig; use serde::de::DeserializeOwned; use std::collections::HashMap; @@ -20,6 +24,24 @@ impl RadioBrowserAPI { .map(|api| RadioBrowserAPI { api }) } + pub fn get_server_status(&mut self) -> Result> { + task::block_on(async { self.api.get_server_status().await }) + } + + pub fn get_server_config(&mut self) -> Result> { + task::block_on(async { self.api.get_server_config().await }) + } + + /// Add a click to a station found by stationuuid + pub fn station_click>(&mut self, stationuuid: P) -> Result> { + task::block_on(async { self.api.station_click(stationuuid).await }) + } + + /// Add a vote to a station found by a stationuuid + pub fn station_vote>(&mut self, stationuuid: P) -> Result> { + task::block_on(async { self.api.station_vote(stationuuid).await }) + } + pub fn get_stations(&self) -> StationSearchBuilder { StationSearchBuilder::new(self.api.get_stations()) } diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs index acea445..9e5dcdf 100644 --- a/src/blocking/mod.rs +++ b/src/blocking/mod.rs @@ -1,10 +1,11 @@ -pub mod api; -pub mod countrysearchbuilder; -pub mod languagesearchbuilder; -pub mod stationsearchbuilder; -pub mod tagsearchbuilder; +mod api; +mod countrysearchbuilder; +mod languagesearchbuilder; +mod stationsearchbuilder; +mod tagsearchbuilder; pub use api::RadioBrowserAPI; pub use countrysearchbuilder::CountrySearchBuilder; pub use languagesearchbuilder::LanguageSearchBuilder; pub use tagsearchbuilder::TagSearchBuilder; +pub use stationsearchbuilder::StationSearchBuilder; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 3c144b3..b0a1945 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ //! ``` mod api; +#[doc()] #[cfg(feature = "blocking")] pub mod blocking; mod external; @@ -66,7 +67,13 @@ pub use structs::ApiCountry; pub use structs::ApiLanguage; pub use structs::ApiStation; pub use structs::ApiStreamingServer; +pub use structs::ApiStationClick; +pub use structs::ApiStationHistory; pub use structs::ApiTag; +pub use structs::ApiStatus; +pub use structs::ApiStationAddResult; +pub use structs::ApiStationClickResult; +pub use structs::ApiStationVoteResult; pub use stationsearchbuilder::StationSearchBuilder; pub use stationsearchbuilder::StationOrder; pub use countrysearchbuilder::CountrySearchBuilder; diff --git a/src/structs.rs b/src/structs.rs index f2aa1bd..240b34c 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,7 +1,48 @@ -use serde::Deserialize; use chrono::DateTime; use chrono::Utc; +use serde::Deserialize; +/// Radiobrowser status and statistical information of single server. +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStatus { + pub supported_version: u32, + pub software_version: Option, + pub status: String, + pub stations: u64, + pub stations_broken: u64, + pub tags: u64, + pub clicks_last_hour: u64, + pub clicks_last_day: u64, + pub languages: u64, + pub countries: u64, +} + +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStationAddResult { + pub ok: bool, + pub message: String, + pub uuid: String, +} + +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStationVoteResult { + pub ok: bool, + pub message: String, +} + +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStationClickResult { + pub ok: bool, + pub message: String, +} + +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiCodec { + name: String, + stationcount: u64, +} + +/// A single station entry #[derive(PartialEq, Deserialize, Debug)] pub struct ApiStation { pub changeuuid: String, @@ -42,6 +83,51 @@ pub struct ApiStation { pub has_extended_info: Option, } +/// A single historical entry for a station +#[derive(PartialEq, Deserialize, Debug)] +pub struct ApiStationHistory { + pub changeuuid: String, + pub stationuuid: String, + pub name: String, + pub url: String, + pub homepage: String, + pub favicon: String, + pub tags: String, + pub country: String, + pub countrycode: String, + pub state: String, + pub language: String, + pub languagecodes: Option, + pub votes: i32, + pub lastchangetime: String, + pub lastchangetime_iso8601: Option>, + pub geo_lat: Option, + pub geo_long: Option, +} + +/// A click event for a station +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStationClick { + pub stationuuid: String, + pub clickuuid: String, + pub clicktimestamp_iso8601: Option>, + pub clicktimestamp: String, +} + +/// A single step of a check action for a station +#[derive(PartialEq, Eq, Deserialize, Debug)] +pub struct ApiStationCheckStep { + pub stepuuid: String, + pub parent_stepuuid: Option, + pub checkuuid: String, + pub stationuuid: String, + pub url: String, + pub urltype: Option, + pub error: Option, + pub creation_iso8601: DateTime, +} + +/// A single country #[derive(PartialEq, Eq, Deserialize, Debug)] pub struct ApiCountry { pub name: String, @@ -49,6 +135,7 @@ pub struct ApiCountry { pub stationcount: u32, } +/// A single language #[derive(PartialEq, Eq, Deserialize, Debug)] pub struct ApiLanguage { pub name: String, @@ -56,13 +143,14 @@ pub struct ApiLanguage { pub stationcount: u32, } +/// A single tag #[derive(PartialEq, Eq, Deserialize, Debug)] pub struct ApiTag { pub name: String, pub stationcount: u32, } -#[derive(PartialEq, Deserialize, Debug)] +#[derive(PartialEq, Eq, Deserialize, Debug)] pub struct ApiStreamingServer { pub uuid: String, pub url: String, @@ -71,7 +159,7 @@ pub struct ApiStreamingServer { pub error: Option, } -#[derive(Debug, Deserialize)] +#[derive(PartialEq, Eq, Deserialize, Debug)] pub struct ApiConfig { pub check_enabled: bool, pub prometheus_exporter_enabled: bool, @@ -95,4 +183,4 @@ pub struct ApiConfig { pub cache_ttl: u32, pub language_replace_filepath: String, pub language_to_code_filepath: String, -} \ No newline at end of file +}