Skip to main content

rustifydl/
spotify.rs

1//! Spotify helpers for resolving tracks, albums, and playlists.
2//!
3//! Input: a Spotify ID string and authenticated client credentials (provided
4//! via [`DownloadOptions`]).
5//! Output: a `HashMap<String, Track>` keyed by a human-friendly display name,
6//! e.g. `"Artists - Title"` or with dupes, if there are two of the same file `"Artists - Album - Title"`.
7
8use crate::DownloadOptions;
9use log::info;
10use spotify_rs::model::track::Track;
11use spotify_rs::{ClientCredsClient, model::PlayableItem};
12use std::collections::HashMap;
13
14/// Fetch a single track by Spotify ID.
15///
16/// Returns a map with one entry mapping a display name to its `spotify_rs::model::track::Track`.
17pub async fn fetch_track(
18    id: &str,
19    options: &DownloadOptions,
20) -> Result<HashMap<String, Track>, Box<dyn std::error::Error + Send + Sync>> {
21    let spotify =
22        ClientCredsClient::authenticate(&options.client_id, &options.client_secret).await?;
23    let track = spotify_rs::track(id).get(&spotify).await?;
24    let mut songs = HashMap::<String, Track>::new();
25    songs.insert(
26        format!(
27            "{} - {}",
28            track
29                .artists
30                .iter()
31                .map(|artist| artist.name.as_str())
32                .collect::<Vec<_>>()
33                .join(", "),
34            track.name,
35        ),
36        track,
37    );
38    Ok(songs)
39}
40
41/// Fetch all tracks from a playlist by ID.
42///
43/// The result map keys are display names. If `DownloadOptions::no_dupes` is
44/// false and a duplicate title is encountered, the album name is appended to
45/// disambiguate entries.
46/// Returns a HashMap with the track name as the key and the `` object.
47pub async fn fetch_playlist(
48    id: &str,
49    options: &DownloadOptions,
50) -> Result<HashMap<String, Track>, Box<dyn std::error::Error + Send + Sync>> {
51    let spotify =
52        ClientCredsClient::authenticate(&options.client_id, &options.client_secret).await?;
53    let mut songs = HashMap::<String, Track>::new();
54
55    let playlist = spotify_rs::playlist(id).get(&spotify).await?;
56    let tracks = playlist.tracks.get_all(&spotify).await?;
57    for song in tracks {
58        if let Some(song) = song {
59            match song.track {
60                PlayableItem::Track(track) => {
61                    if songs.contains_key(
62                        format!(
63                            "{} - {}",
64                            track
65                                .artists
66                                .iter()
67                                .map(|artist| artist.name.as_str())
68                                .collect::<Vec<_>>()
69                                .join(", "),
70                            track.name,
71                        )
72                        .as_str(),
73                    ) && !options.no_dupes
74                    {
75                        songs.insert(
76                            format!(
77                                "{} - {} - {}",
78                                track
79                                    .artists
80                                    .iter()
81                                    .map(|artist| artist.name.as_str())
82                                    .collect::<Vec<_>>()
83                                    .join(", "),
84                                track.album.name,
85                                track.name,
86                            ),
87                            track,
88                        );
89                    } else {
90                        songs.insert(
91                            format!(
92                                "{} - {}",
93                                track
94                                    .artists
95                                    .iter()
96                                    .map(|artist| artist.name.as_str())
97                                    .collect::<Vec<_>>()
98                                    .join(", "),
99                                track.name,
100                            ),
101                            track,
102                        );
103                    }
104                }
105                PlayableItem::Episode(_episode) => {}
106            }
107        } else {
108            info!("No song found.");
109        }
110    }
111    info!("Found {} tracks in {}!", songs.len(), playlist.name);
112    Ok(songs)
113}
114
115/// Fetch all tracks from a Album by ID.
116///
117/// The result map keys are display names. If `DownloadOptions::no_dupes` is
118/// false and a duplicate title is encountered, the album name is appended to
119/// disambiguate entries.
120/// Returns a HashMap with the track name as the key and the `spotify_rs::model::track::Track` object.
121pub async fn fetch_album(
122    id: &str,
123    options: &DownloadOptions,
124) -> Result<HashMap<String, Track>, Box<dyn std::error::Error + Send + Sync>> {
125    let spotify =
126        ClientCredsClient::authenticate(&options.client_id, &options.client_secret).await?;
127    let mut songs = HashMap::<String, Track>::new();
128
129    let album = spotify_rs::album(id).get(&spotify).await?;
130    let tracks = album.tracks.get_all(&spotify).await?;
131
132    for song in tracks {
133        if let Some(song) = song {
134            let track = spotify_rs::track(song.id).get(&spotify).await?;
135            songs.insert(
136                format!(
137                    "{} - {}",
138                    track
139                        .artists
140                        .iter()
141                        .map(|artist| artist.name.as_str())
142                        .collect::<Vec<_>>()
143                        .join(", "),
144                    track.name,
145                ),
146                track,
147            );
148        } else {
149            info!("No song found.");
150        }
151    }
152    info!("Found {} tracks in {}!", songs.len(), album.name);
153    Ok(songs)
154}