#[cfg(feature="http-client")] extern crate reqwest;
#[cfg(feature="http-client")] extern crate url;
#[macro_use] extern crate failure;
extern crate serde_json;
use std::io;
use std::result;
use data::Playlist;
use data::ProgramList;
use data::RadioChannel;
pub mod http;
pub mod data {
use std::collections::HashMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RadioChannel {
RadioRock,
RadioSuomipop,
LOOP,
HitMix,
Helmi,
AitoIskelma,
GrooveFM,
Other(String),
}
impl RadioChannel {
pub fn as_str(&self) -> &str {
match *self {
RadioChannel::RadioRock => "52",
RadioChannel::RadioSuomipop => "53",
RadioChannel::LOOP => "54",
RadioChannel::HitMix => "55",
RadioChannel::Helmi => "57",
RadioChannel::AitoIskelma => "58",
RadioChannel::GrooveFM => "70",
RadioChannel::Other(ref other) => other,
}
}
}
impl Serialize for RadioChannel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer
{
serializer.serialize_str(match *self {
RadioChannel::RadioRock => self.as_str(),
RadioChannel::RadioSuomipop => self.as_str(),
RadioChannel::LOOP => self.as_str(),
RadioChannel::HitMix => self.as_str(),
RadioChannel::Helmi => self.as_str(),
RadioChannel::AitoIskelma => self.as_str(),
RadioChannel::GrooveFM => self.as_str(),
RadioChannel::Other(ref other) => other,
})
}
}
impl<'de> Deserialize<'de> for RadioChannel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
Ok(match s.as_str() {
"52" => RadioChannel::RadioRock,
"53" => RadioChannel::RadioSuomipop,
"54" => RadioChannel::LOOP,
"55" => RadioChannel::HitMix,
"57" => RadioChannel::Helmi,
"58" => RadioChannel::AitoIskelma,
"70" => RadioChannel::GrooveFM,
_ => RadioChannel::Other(s),
})
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Program {
pub title: String,
pub start_time: i64,
pub end_time: i64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProgramList(pub HashMap<RadioChannel, Vec<Program>>);
impl ProgramList {
pub fn get(&self) -> &HashMap<RadioChannel, Vec<Program>> {
&self.0
}
pub fn get_mut(&mut self) -> &mut HashMap<RadioChannel, Vec<Program>> {
&mut self.0
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Track {
pub timestamp: i64,
pub date: String,
pub channel: i32,
pub artist: String,
pub song: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Playlist {
pub items: Vec<Track>,
pub next_token: i64,
}
}
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "HTTP Error")]
HTTPError,
#[fail(display = "IO Error: {}", _0)]
IOError(#[cause] io::Error),
#[fail(display = "JSON Error: {}", _0)]
JSONError(#[cause] serde_json::error::Error),
#[fail(display = "JSON Path Error")]
JSONPathError,
#[fail(display = "Invalid Parameter: {}", _0)]
InvalidParameter(String),
}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug, Clone)]
pub struct SuplAPI<A: http::HttpClient> {
pub client: A,
pub base_url: String,
}
impl<A: http::HttpClient + Default> Default for SuplAPI<A> {
fn default() -> Self {
SuplAPI::new(A::default())
}
}
impl<A: http::HttpClient> SuplAPI<A> {
pub fn new(mut client: A) -> Self {
client.user_agent("suplapi ()".to_owned());
SuplAPI {
client: client,
base_url: ".nm-services.nelonenmedia.fi".to_owned(),
}
}
fn playlist_url(&self) -> String {
format!("{}{}", "https://supla-playlist", self.base_url)
}
fn prod_component_url(&self) -> String {
format!("{}{}", "https://prod-component-api", self.base_url)
}
async fn query<'a, I>(&self, base_url: String, args: I) -> Result<serde_json::Value>
where I: Iterator<Item=(&'a str, &'a str)> + Send {
let resp = self.client.get(&base_url, args).await.map_err(|_| Error::HTTPError)?;
let json = serde_json::from_str(&resp).map_err(Error::JSONError)?;
Ok(json)
}
pub async fn playlist(&self, channel: RadioChannel, limit: i32, next_token: Option<i64>) -> Result<Playlist> {
let url = format!("{}{}", self.playlist_url(), "/playlist?");
let data: Playlist;
if let Some(token) = next_token {
data = serde_json::from_value(self.query(url, vec![
("channel", channel.as_str()),
("limit", format!("{}", limit).as_str()),
("next_token", format!("{}", token).as_str()),
].into_iter()).await?).map_err(Error::JSONError)?;
} else {
data = serde_json::from_value(self.query(url, vec![
("channel", channel.as_str()),
("limit", format!("{}", limit).as_str()),
].into_iter()).await?).map_err(Error::JSONError)?;
}
Ok(data)
}
pub async fn program_list(&self) -> Result<ProgramList> {
let url = format!("{}{}", self.prod_component_url(), "/api/radio-programs");
let data: ProgramList = serde_json::from_value(self.query(url, vec![].into_iter()).await?).map_err(Error::JSONError)?;
Ok(data)
}
}