use std::collections::HashMap;
use std::convert::TryFrom;
use pandora_api_derive::PandoraJsonRequest;
use serde::{Deserialize, Serialize};
use crate::errors::Error;
use crate::json::errors::JsonError;
use crate::json::{PandoraJsonApiRequest, PandoraSession, Timestamp};
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct AddFeedback {
pub station_token: String,
pub track_token: String,
pub is_positive: bool,
}
impl AddFeedback {
pub fn new(station_token: &str, track_token: &str, is_positive: bool) -> Self {
Self {
station_token: station_token.to_string(),
track_token: track_token.to_string(),
is_positive,
}
}
pub fn new_positive(station_token: &str, track_token: &str) -> Self {
Self::new(station_token, track_token, true)
}
pub fn new_negative(station_token: &str, track_token: &str) -> Self {
Self::new(station_token, track_token, false)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddFeedbackResponse {
pub date_created: Timestamp,
pub music_token: String,
pub total_thumbs_up: u32,
pub total_thumbs_down: u32,
pub feedback_id: String,
pub is_positive: bool,
pub song_name: String,
pub artist_name: String,
pub album_art_url: String,
pub station_personalization_percent: u8,
}
pub async fn add_feedback(
session: &mut PandoraSession,
station_token: &str,
track_token: &str,
is_positive: bool,
) -> Result<AddFeedbackResponse, Error> {
AddFeedback::new(station_token, track_token, is_positive)
.response(session)
.await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct AddMusic {
pub station_token: String,
pub music_token: String,
}
impl AddMusic {
pub fn new(station_token: &str, music_token: &str) -> Self {
Self {
station_token: station_token.to_string(),
music_token: music_token.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AddMusicResponse {
pub artist_name: String,
pub music_token: String,
pub seed_id: String,
pub art_url: String,
}
pub async fn add_music(
session: &mut PandoraSession,
station_token: &str,
music_token: &str,
) -> Result<AddMusicResponse, Error> {
AddMusic::new(station_token, music_token)
.response(session)
.await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct CreateStation {
pub track_token: String,
pub music_type: MusicType,
pub music_token: String,
}
impl CreateStation {
pub fn new_from_track(track_token: &str, music_type: MusicType) -> Self {
Self {
track_token: track_token.to_string(),
music_type,
music_token: String::new(),
}
}
pub fn new_from_music_token(music_token: &str) -> Self {
Self {
track_token: String::new(),
music_type: MusicType::Artist,
music_token: music_token.to_string(),
}
}
pub fn new_from_track_song(track_token: &str) -> Self {
Self::new_from_track(track_token, MusicType::Song)
}
pub fn new_from_track_artist(track_token: &str) -> Self {
Self::new_from_track(track_token, MusicType::Artist)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MusicType {
Song,
Artist,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateStationResponse {
pub station_token: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn create_station_from_track_song(
session: &mut PandoraSession,
track_token: &str,
) -> Result<CreateStationResponse, Error> {
CreateStation::new_from_track_song(track_token)
.response(session)
.await
}
pub async fn create_station_from_artist(
session: &mut PandoraSession,
track_token: &str,
) -> Result<CreateStationResponse, Error> {
CreateStation::new_from_track_artist(track_token)
.response(session)
.await
}
pub async fn create_station_from_music_token(
session: &mut PandoraSession,
music_token: &str,
) -> Result<CreateStationResponse, Error> {
CreateStation::new_from_music_token(music_token)
.response(session)
.await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct DeleteFeedback {
pub feedback_id: String,
}
impl<TS: ToString> From<&TS> for DeleteFeedback {
fn from(feedback_id: &TS) -> Self {
Self {
feedback_id: feedback_id.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteFeedbackResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn delete_feedback(
session: &mut PandoraSession,
feedback_id: &str,
) -> Result<DeleteFeedbackResponse, Error> {
DeleteFeedback::from(&feedback_id).response(session).await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct DeleteMusic {
pub seed_id: String,
}
impl<TS: ToString> From<&TS> for DeleteMusic {
fn from(seed_id: &TS) -> Self {
Self {
seed_id: seed_id.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteMusicResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn delete_music(
session: &mut PandoraSession,
seed_id: &str,
) -> Result<DeleteMusicResponse, Error> {
DeleteMusic::from(&seed_id).response(session).await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct DeleteStation {
pub station_token: String,
}
impl<TS: ToString> From<&TS> for DeleteStation {
fn from(station_token: &TS) -> Self {
Self {
station_token: station_token.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeleteStationResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn delete_station(
session: &mut PandoraSession,
station_token: &str,
) -> Result<DeleteStationResponse, Error> {
DeleteStation::from(&station_token).response(session).await
}
#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct GetGenreStationsChecksum {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
impl GetGenreStationsChecksum {
pub fn new() -> Self {
Self::default()
}
pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
self.optional
.insert(option.to_string(), serde_json::value::Value::from(value));
self
}
pub fn include_genre_category_ad_url(self, value: bool) -> Self {
self.and_boolean_option("includeGenreCategoryAdUrl", value)
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetGenreStationsChecksumResponse {
pub checksum: String,
}
pub async fn get_genre_stations_checksum(
session: &mut PandoraSession,
) -> Result<GetGenreStationsChecksumResponse, Error> {
GetGenreStationsChecksum::default()
.include_genre_category_ad_url(false)
.response(session)
.await
}
#[derive(Debug, Clone, Default, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct GetGenreStations {}
impl GetGenreStations {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetGenreStationsResponse {
pub categories: Vec<GenreCategory>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenreCategory {
pub category_name: String,
pub stations: Vec<GenreStation>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenreStation {
pub station_token: String,
pub station_name: String,
pub station_id: String,
}
pub async fn get_genre_stations(
session: &mut PandoraSession,
) -> Result<GetGenreStationsResponse, Error> {
GetGenreStations::default().response(session).await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct GetPlaylist {
pub station_token: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
impl GetPlaylist {
pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
self.optional
.insert(option.to_string(), serde_json::value::Value::from(value));
self
}
pub fn additional_audio_url(mut self, value: &str) -> Self {
self.optional
.entry("additionalAudioUrl".to_string())
.and_modify(|s| {
if let serde_json::value::Value::String(s) = s {
s.push(',');
s.push_str(value);
}
})
.or_insert_with(|| serde_json::value::Value::from(value));
self
}
pub fn station_is_starting(self, value: bool) -> Self {
self.and_boolean_option("stationIsStarting", value)
}
pub fn include_track_length(self, value: bool) -> Self {
self.and_boolean_option("includeTrackLength", value)
}
pub fn include_audio_token(self, value: bool) -> Self {
self.and_boolean_option("includeAudioToken", value)
}
pub fn xplatform_ad_capable(self, value: bool) -> Self {
self.and_boolean_option("xplatformAdCapable", value)
}
pub fn include_audio_receipt_url(self, value: bool) -> Self {
self.and_boolean_option("includeAudioReceiptUrl", value)
}
pub fn include_backstage_ad_url(self, value: bool) -> Self {
self.and_boolean_option("includeBackstageAdUrl", value)
}
pub fn include_sharing_ad_url(self, value: bool) -> Self {
self.and_boolean_option("includeSharingAdUrl", value)
}
pub fn include_social_ad_url(self, value: bool) -> Self {
self.and_boolean_option("includeSocialAdUrl", value)
}
pub fn include_competitive_sep_indicator(self, value: bool) -> Self {
self.and_boolean_option("includeCompetitiveSepIndicator", value)
}
pub fn include_complete_playlist(self, value: bool) -> Self {
self.and_boolean_option("includeCompletePlaylist", value)
}
pub fn include_track_options(self, value: bool) -> Self {
self.and_boolean_option("includeTrackOptions", value)
}
pub fn audio_ad_pod_capable(self, value: bool) -> Self {
self.and_boolean_option("audioAdPodCapable", value)
}
}
impl<TS: ToString> From<&TS> for GetPlaylist {
fn from(station_token: &TS) -> Self {
Self {
station_token: station_token.to_string(),
optional: HashMap::new(),
}
.additional_audio_url(&AudioFormat::Mp3128.to_string())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AudioFormat {
AacMono40,
Aac64,
AacPlus32,
AacPlus64,
AacPlusAdts24,
AacPlusAdts32,
AacPlusAdts64,
Mp3128,
Wma32,
}
impl AudioFormat {
pub fn new_from_audio_url_map(encoding: &str, bitrate: &str) -> Result<Self, Error> {
match (encoding, bitrate) {
("aac", "64") => Ok(Self::AacPlus64),
("aacplus", "32") => Ok(Self::AacPlus32),
("aacplus", "64") => Ok(Self::AacPlus64),
_ => Err(
JsonError::new(None, Some(String::from("Unsupported audioUrlMap format"))).into(),
),
}
}
pub fn get_extension(&self) -> String {
match self {
Self::AacMono40 => String::from("m4a"),
Self::Aac64 => String::from("m4a"),
Self::AacPlus32 => String::from("m4a"),
Self::AacPlus64 => String::from("m4a"),
Self::AacPlusAdts24 => String::from("aac"),
Self::AacPlusAdts32 => String::from("aac"),
Self::AacPlusAdts64 => String::from("aac"),
Self::Mp3128 => String::from("mp3"),
Self::Wma32 => String::from("wma"),
}
}
pub fn get_bitrate(&self) -> u32 {
match self {
Self::AacMono40 => 40,
Self::Aac64 => 64,
Self::AacPlus32 => 32,
Self::AacPlus64 => 64,
Self::AacPlusAdts24 => 24,
Self::AacPlusAdts32 => 32,
Self::AacPlusAdts64 => 64,
Self::Mp3128 => 128,
Self::Wma32 => 32,
}
}
fn get_quality_weight(&self) -> u8 {
match self {
Self::AacPlusAdts64 => 10,
Self::AacPlus64 => 9,
Self::Mp3128 => 8,
Self::Aac64 => 7,
Self::AacPlusAdts32 => 6,
Self::AacPlus32 => 5,
Self::AacPlusAdts24 => 4,
Self::AacMono40 => 2,
Self::Wma32 => 1,
}
}
}
impl PartialOrd for AudioFormat {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.get_quality_weight().cmp(&other.get_quality_weight()))
}
}
impl std::fmt::Display for AudioFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AudioFormat::AacMono40 => write!(f, "HTTP_40_AAC_MONO"),
AudioFormat::Aac64 => write!(f, "HTTP_64_AAC"),
AudioFormat::AacPlus32 => write!(f, "HTTP_32_AACPLUS"),
AudioFormat::AacPlus64 => write!(f, "HTTP_64_AACPLUS"),
AudioFormat::AacPlusAdts24 => write!(f, "HTTP_24_AACPLUS_ADTS"),
AudioFormat::AacPlusAdts32 => write!(f, "HTTP_32_AACPLUS_ADTS"),
AudioFormat::AacPlusAdts64 => write!(f, "HTTP_64_AACPLUS_ADTS"),
AudioFormat::Mp3128 => write!(f, "HTTP_128_MP3"),
AudioFormat::Wma32 => write!(f, "HTTP_32_WMA"),
}
}
}
impl TryFrom<&str> for AudioFormat {
type Error = Error;
fn try_from(fmt: &str) -> std::result::Result<Self, Self::Error> {
match fmt {
"HTTP_40_AAC_MONO" => Ok(AudioFormat::AacMono40),
"HTTP_64_AAC" => Ok(AudioFormat::Aac64),
"HTTP_32_AACPLUS" => Ok(AudioFormat::AacPlus32),
"HTTP_64_AACPLUS" => Ok(AudioFormat::AacPlus64),
"HTTP_24_AACPLUS_ADTS" => Ok(AudioFormat::AacPlusAdts24),
"HTTP_32_AACPLUS_ADTS" => Ok(AudioFormat::AacPlusAdts32),
"HTTP_64_AACPLUS_ADTS" => Ok(AudioFormat::AacPlusAdts64),
"HTTP_128_MP3" => Ok(AudioFormat::Mp3128),
"HTTP_32_WMA" => Ok(AudioFormat::Wma32),
x => Err(Self::Error::InvalidAudioFormat(x.to_string())),
}
}
}
impl TryFrom<String> for AudioFormat {
type Error = Error;
fn try_from(fmt: String) -> std::result::Result<Self, Self::Error> {
Self::try_from(fmt.as_str())
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetPlaylistResponse {
pub items: Vec<PlaylistEntry>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum PlaylistEntry {
PlaylistAd(PlaylistAd),
PlaylistTrack(Box<PlaylistTrack>),
}
impl PlaylistEntry {
pub fn is_ad(&self) -> bool {
matches!(self, PlaylistEntry::PlaylistAd(_))
}
pub fn is_track(&self) -> bool {
matches!(self, PlaylistEntry::PlaylistTrack(_))
}
pub fn get_ad(&self) -> Option<PlaylistAd> {
match self {
PlaylistEntry::PlaylistAd(a) => Some(a.clone()),
_ => None,
}
}
pub fn get_track(&self) -> Option<PlaylistTrack> {
match self {
PlaylistEntry::PlaylistTrack(t) => Some(*t.clone()),
_ => None,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistAd {
pub ad_token: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistTrack {
pub track_token: String,
pub music_id: String,
pub station_id: String,
pub audio_url_map: AudioQuality,
pub artist_name: String,
pub album_name: String,
pub song_name: String,
pub song_rating: u32,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioQuality {
pub high_quality: AudioStream,
pub medium_quality: AudioStream,
pub low_quality: AudioStream,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioStream {
pub bitrate: String,
pub encoding: String,
pub audio_url: String,
pub protocol: String,
}
pub async fn get_playlist(
session: &mut PandoraSession,
station_token: &str,
) -> Result<GetPlaylistResponse, Error> {
GetPlaylist::from(&station_token)
.station_is_starting(false)
.include_track_length(false)
.include_audio_token(false)
.xplatform_ad_capable(false)
.include_audio_receipt_url(false)
.include_backstage_ad_url(false)
.include_sharing_ad_url(false)
.include_social_ad_url(false)
.include_competitive_sep_indicator(false)
.include_complete_playlist(false)
.include_track_options(false)
.audio_ad_pod_capable(false)
.response(session)
.await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct GetStation {
pub station_token: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
impl GetStation {
pub fn and_boolean_option(mut self, option: &str, value: bool) -> Self {
self.optional
.insert(option.to_string(), serde_json::value::Value::from(value));
self
}
pub fn include_extended_attributes(self, value: bool) -> Self {
self.and_boolean_option("includeExtendedAttributes", value)
}
}
impl<TS: ToString> From<&TS> for GetStation {
fn from(station_token: &TS) -> Self {
GetStation {
station_token: station_token.to_string(),
optional: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetStationResponse {
pub station_id: String,
pub station_token: String,
pub station_name: String,
pub allow_add_music: Option<bool>,
pub suppress_video_ads: Option<bool>,
pub date_created: Timestamp,
pub station_detail_url: Option<String>,
pub art_url: Option<String>,
pub requires_clean_ads: Option<bool>,
pub music: Option<StationSeeds>,
pub is_shared: Option<bool>,
pub allow_delete: Option<bool>,
#[serde(default)]
pub genre: Vec<String>,
pub is_quick_mix: Option<bool>,
pub allow_rename: Option<bool>,
pub station_sharing_url: Option<String>,
pub allow_edit_description: Option<bool>,
pub feedback: Option<StationFeedback>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StationSeeds {
pub songs: Vec<SongSeed>,
pub artists: Vec<ArtistSeed>,
pub genres: Vec<GenreSeed>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SongSeed {
pub seed_id: String,
pub music_token: String,
pub song_name: String,
pub artist_name: String,
pub pandora_type: String,
pub pandora_id: String,
pub art_url: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistSeed {
pub seed_id: String,
pub music_token: String,
pub artist_name: String,
pub pandora_type: String,
pub pandora_id: String,
pub icon: HashMap<String, String>,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenreSeed {
pub seed_id: String,
pub music_token: String,
pub genre_name: String,
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StationFeedback {
pub thumbs_up: Vec<TrackFeedback>,
pub total_thumbs_up: u32,
pub thumbs_down: Vec<TrackFeedback>,
pub total_thumbs_down: u32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrackFeedback {
pub feedback_id: String,
pub song_name: String,
pub artist_name: String,
pub is_positive: bool,
pub music_token: String,
pub date_created: Timestamp,
pub album_art_url: String,
}
pub async fn get_station(
session: &mut PandoraSession,
station_token: &str,
) -> Result<GetStationResponse, Error> {
GetStation::from(&station_token)
.include_extended_attributes(false)
.response(session)
.await
}
pub struct PublishStationShareUnsupported {}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct RenameStation {
pub station_token: String,
pub station_name: String,
}
impl RenameStation {
pub fn new(station_token: &str, station_name: &str) -> Self {
Self {
station_token: station_token.to_string(),
station_name: station_name.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameStationResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn rename_station(
session: &mut PandoraSession,
station_token: &str,
station_name: &str,
) -> Result<RenameStationResponse, Error> {
RenameStation::new(station_token, station_name)
.response(session)
.await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct ShareStation {
pub station_id: String,
pub station_token: String,
pub emails: Vec<String>,
}
impl ShareStation {
pub fn new(station_id: &str, station_token: &str) -> Self {
Self {
station_id: station_id.to_string(),
station_token: station_token.to_string(),
emails: Vec::new(),
}
}
pub fn add_recipient(&mut self, recipient: &str) {
self.emails.push(recipient.to_string());
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ShareStationResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn share_station(
session: &mut PandoraSession,
station_id: &str,
station_token: &str,
emails: Vec<String>,
) -> Result<ShareStationResponse, Error> {
let mut request = ShareStation::new(station_id, station_token);
request.emails = emails;
request.response(session).await
}
#[derive(Debug, Clone, Serialize, PandoraJsonRequest)]
#[pandora_request(encrypted = true)]
#[serde(rename_all = "camelCase")]
pub struct TransformSharedStation {
pub station_token: String,
}
impl<TS: ToString> From<&TS> for TransformSharedStation {
fn from(station_token: &TS) -> Self {
Self {
station_token: station_token.to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransformSharedStationResponse {
#[serde(flatten)]
pub optional: HashMap<String, serde_json::value::Value>,
}
pub async fn transform_shared_station(
session: &mut PandoraSession,
station_token: &str,
) -> Result<TransformSharedStationResponse, Error> {
TransformSharedStation::from(&station_token)
.response(session)
.await
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::json::{
music::search, music::ArtistMatch, tests::session_login, user::get_station_list, Partner,
};
#[tokio::test]
async fn station_ops_test() {
let partner = Partner::default();
let mut session = session_login(&partner)
.await
.expect("Failed initializing login session");
let artist_search = search(&mut session, "INXS")
.await
.expect("Failed completing artist search request");
let additional_artist_search = search(&mut session, "Panic! At the Disco")
.await
.expect("Failed completing artist search request");
if let Some(ArtistMatch { music_token, .. }) = artist_search
.artists
.iter()
.filter(|am| am.score == 100)
.next()
{
let created_station = create_station_from_music_token(&mut session, &music_token)
.await
.expect("Failed creating station from search result");
let _renamed_station =
rename_station(&mut session, &created_station.station_token, "XSNI Radio")
.await
.expect("Failed renaming station");
if let Some(ArtistMatch { music_token, .. }) = additional_artist_search
.artists
.iter()
.filter(|am| am.score == 100)
.next()
{
let added_music =
add_music(&mut session, &created_station.station_token, music_token)
.await
.expect("Failed adding music to station");
let _del_music = delete_music(&mut session, &added_music.seed_id)
.await
.expect("Failed deleting music from station");
}
let _del_station = delete_station(&mut session, &created_station.station_token)
.await
.expect("Failed deleting station");
}
}
#[tokio::test]
async fn station_feedback_test() {
let partner = Partner::default();
let mut session = session_login(&partner)
.await
.expect("Failed initializing login session");
for station in get_station_list(&mut session)
.await
.expect("Failed getting station list to look up a track to bookmark")
.stations
{
let station = GetStation::from(&station.station_token)
.include_extended_attributes(true)
.response(&mut session)
.await
.expect("Failed getting station attributes");
let mut protected_tracks: HashSet<String> = HashSet::new();
protected_tracks.extend(
station
.feedback
.iter()
.flat_map(|f| f.thumbs_up.iter())
.map(|tf| tf.song_name.clone()),
);
protected_tracks.extend(
station
.feedback
.iter()
.flat_map(|f| f.thumbs_down.iter())
.map(|tf| tf.song_name.clone()),
);
for track in get_playlist(&mut session, &station.station_token)
.await
.expect("Failed completing request for playlist")
.items
.iter()
.flat_map(|p| p.get_track())
{
if protected_tracks.contains(&track.song_name) {
continue;
}
let feedback = add_feedback(
&mut session,
&station.station_token,
&track.track_token,
true,
)
.await
.expect("Failed adding positive feedback to track");
let _del_feedback = delete_feedback(&mut session, &feedback.feedback_id)
.await
.expect("Failed deleting positive feedback from track");
let feedback = add_feedback(
&mut session,
&station.station_token,
&track.track_token,
false,
)
.await
.expect("Failed adding negative feedback to track");
let _del_feedback = delete_feedback(&mut session, &feedback.feedback_id)
.await
.expect("Failed deleting negative feedback from track");
return;
}
}
panic!("Station list request returned no results, so no feedback-capable content.");
}
}