1use std::{
2 fmt::Debug,
3 ops::{Deref, DerefMut},
4};
5
6use crate::{
7 album::Albums,
8 availability::Availabilities,
9 external_id::ExternalIds,
10 image::Images,
11 request::RequestResult,
12 restriction::Restrictions,
13 sale_period::SalePeriods,
14 track::Tracks,
15 util::{impl_deref_wrapped, impl_from_repeated, impl_try_from_repeated},
16 Metadata,
17};
18
19use librespot_core::{Error, Session, SpotifyId};
20
21use librespot_protocol as protocol;
22pub use protocol::metadata::artist_with_role::ArtistRole;
23
24use protocol::metadata::ActivityPeriod as ActivityPeriodMessage;
25use protocol::metadata::AlbumGroup as AlbumGroupMessage;
26use protocol::metadata::ArtistWithRole as ArtistWithRoleMessage;
27use protocol::metadata::Biography as BiographyMessage;
28use protocol::metadata::TopTracks as TopTracksMessage;
29
30#[derive(Debug, Clone)]
31pub struct Artist {
32 pub id: SpotifyId,
33 pub name: String,
34 pub popularity: i32,
35 pub top_tracks: CountryTopTracks,
36 pub albums: AlbumGroups,
37 pub singles: AlbumGroups,
38 pub compilations: AlbumGroups,
39 pub appears_on_albums: AlbumGroups,
40 pub genre: Vec<String>,
41 pub external_ids: ExternalIds,
42 pub portraits: Images,
43 pub biographies: Biographies,
44 pub activity_periods: ActivityPeriods,
45 pub restrictions: Restrictions,
46 pub related: Artists,
47 pub is_portrait_album_cover: bool,
48 pub portrait_group: Images,
49 pub sales_periods: SalePeriods,
50 pub availabilities: Availabilities,
51}
52
53#[derive(Debug, Clone, Default)]
54pub struct Artists(pub Vec<Artist>);
55
56impl_deref_wrapped!(Artists, Vec<Artist>);
57
58#[derive(Debug, Clone)]
59pub struct ArtistWithRole {
60 pub id: SpotifyId,
61 pub name: String,
62 pub role: ArtistRole,
63}
64
65#[derive(Debug, Clone, Default)]
66pub struct ArtistsWithRole(pub Vec<ArtistWithRole>);
67
68impl_deref_wrapped!(ArtistsWithRole, Vec<ArtistWithRole>);
69
70#[derive(Debug, Clone)]
71pub struct TopTracks {
72 pub country: String,
73 pub tracks: Tracks,
74}
75
76#[derive(Debug, Clone, Default)]
77pub struct CountryTopTracks(pub Vec<TopTracks>);
78
79impl_deref_wrapped!(CountryTopTracks, Vec<TopTracks>);
80
81#[derive(Debug, Clone, Default)]
82pub struct AlbumGroup(pub Albums);
83
84impl_deref_wrapped!(AlbumGroup, Albums);
85
86#[derive(Debug, Clone, Default)]
96pub struct AlbumGroups(pub Vec<AlbumGroup>);
97
98impl_deref_wrapped!(AlbumGroups, Vec<AlbumGroup>);
99
100#[derive(Debug, Clone)]
101pub struct Biography {
102 pub text: String,
103 pub portraits: Images,
104 pub portrait_group: Vec<Images>,
105}
106
107#[derive(Debug, Clone, Default)]
108pub struct Biographies(pub Vec<Biography>);
109
110impl_deref_wrapped!(Biographies, Vec<Biography>);
111
112#[derive(Debug, Clone)]
113pub enum ActivityPeriod {
114 Timespan {
115 start_year: u16,
116 end_year: Option<u16>,
117 },
118 Decade(u16),
119}
120
121#[derive(Debug, Clone, Default)]
122pub struct ActivityPeriods(pub Vec<ActivityPeriod>);
123
124impl_deref_wrapped!(ActivityPeriods, Vec<ActivityPeriod>);
125
126impl CountryTopTracks {
127 pub fn for_country(&self, country: &str) -> Tracks {
128 if let Some(country) = self.0.iter().find(|top_track| top_track.country == country) {
129 return country.tracks.clone();
130 }
131
132 if let Some(global) = self.0.iter().find(|top_track| top_track.country.is_empty()) {
133 return global.tracks.clone();
134 }
135
136 Tracks(vec![]) }
138}
139
140impl Artist {
141 pub fn albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
145 self.albums.current_releases()
146 }
147
148 pub fn singles_current(&self) -> impl Iterator<Item = &SpotifyId> {
152 self.singles.current_releases()
153 }
154
155 pub fn compilations_current(&self) -> impl Iterator<Item = &SpotifyId> {
160 self.compilations.current_releases()
161 }
162
163 pub fn appears_on_albums_current(&self) -> impl Iterator<Item = &SpotifyId> {
167 self.appears_on_albums.current_releases()
168 }
169}
170
171#[async_trait]
172impl Metadata for Artist {
173 type Message = protocol::metadata::Artist;
174
175 async fn request(session: &Session, artist_id: &SpotifyId) -> RequestResult {
176 session.spclient().get_artist_metadata(artist_id).await
177 }
178
179 fn parse(msg: &Self::Message, _: &SpotifyId) -> Result<Self, Error> {
180 Self::try_from(msg)
181 }
182}
183
184impl TryFrom<&<Self as Metadata>::Message> for Artist {
185 type Error = librespot_core::Error;
186 fn try_from(artist: &<Self as Metadata>::Message) -> Result<Self, Self::Error> {
187 Ok(Self {
188 id: artist.try_into()?,
189 name: artist.name().to_owned(),
190 popularity: artist.popularity(),
191 top_tracks: artist.top_track.as_slice().try_into()?,
192 albums: artist.album_group.as_slice().try_into()?,
193 singles: artist.single_group.as_slice().try_into()?,
194 compilations: artist.compilation_group.as_slice().try_into()?,
195 appears_on_albums: artist.appears_on_group.as_slice().try_into()?,
196 genre: artist.genre.to_vec(),
197 external_ids: artist.external_id.as_slice().into(),
198 portraits: artist.portrait.as_slice().into(),
199 biographies: artist.biography.as_slice().into(),
200 activity_periods: artist.activity_period.as_slice().try_into()?,
201 restrictions: artist.restriction.as_slice().into(),
202 related: artist.related.as_slice().try_into()?,
203 is_portrait_album_cover: artist.is_portrait_album_cover(),
204 portrait_group: artist
205 .portrait_group
206 .get_or_default()
207 .image
208 .as_slice()
209 .into(),
210 sales_periods: artist.sale_period.as_slice().try_into()?,
211 availabilities: artist.availability.as_slice().try_into()?,
212 })
213 }
214}
215
216impl_try_from_repeated!(<Artist as Metadata>::Message, Artists);
217
218impl TryFrom<&ArtistWithRoleMessage> for ArtistWithRole {
219 type Error = librespot_core::Error;
220 fn try_from(artist_with_role: &ArtistWithRoleMessage) -> Result<Self, Self::Error> {
221 Ok(Self {
222 id: artist_with_role.try_into()?,
223 name: artist_with_role.artist_name().to_owned(),
224 role: artist_with_role.role(),
225 })
226 }
227}
228
229impl_try_from_repeated!(ArtistWithRoleMessage, ArtistsWithRole);
230
231impl TryFrom<&TopTracksMessage> for TopTracks {
232 type Error = librespot_core::Error;
233 fn try_from(top_tracks: &TopTracksMessage) -> Result<Self, Self::Error> {
234 Ok(Self {
235 country: top_tracks.country().to_owned(),
236 tracks: top_tracks.track.as_slice().try_into()?,
237 })
238 }
239}
240
241impl_try_from_repeated!(TopTracksMessage, CountryTopTracks);
242
243impl TryFrom<&AlbumGroupMessage> for AlbumGroup {
244 type Error = librespot_core::Error;
245 fn try_from(album_groups: &AlbumGroupMessage) -> Result<Self, Self::Error> {
246 Ok(Self(album_groups.album.as_slice().try_into()?))
247 }
248}
249
250impl AlbumGroups {
251 pub fn current_releases(&self) -> impl Iterator<Item = &SpotifyId> {
255 self.iter().filter_map(|agrp| agrp.first())
256 }
257}
258
259impl_try_from_repeated!(AlbumGroupMessage, AlbumGroups);
260
261impl From<&BiographyMessage> for Biography {
262 fn from(biography: &BiographyMessage) -> Self {
263 let portrait_group = biography
264 .portrait_group
265 .iter()
266 .map(|it| it.image.as_slice().into())
267 .collect();
268
269 Self {
270 text: biography.text().to_owned(),
271 portraits: biography.portrait.as_slice().into(),
272 portrait_group,
273 }
274 }
275}
276
277impl_from_repeated!(BiographyMessage, Biographies);
278
279impl TryFrom<&ActivityPeriodMessage> for ActivityPeriod {
280 type Error = librespot_core::Error;
281
282 fn try_from(period: &ActivityPeriodMessage) -> Result<Self, Self::Error> {
283 let activity_period = match (
284 period.has_decade(),
285 period.has_start_year(),
286 period.has_end_year(),
287 ) {
288 (true, false, false) => Self::Decade(period.decade().try_into()?),
290 (false, true, closed_period) => Self::Timespan {
291 start_year: period.start_year().try_into()?,
292 end_year: closed_period
293 .then(|| period.end_year().try_into())
294 .transpose()?,
295 },
296 _ => {
297 return Err(librespot_core::Error::failed_precondition(
298 "ActivityPeriod is expected to be either a decade or timespan",
299 ))
300 }
301 };
302 Ok(activity_period)
303 }
304}
305
306impl_try_from_repeated!(ActivityPeriodMessage, ActivityPeriods);