Skip to main content

convert_invert/internals/query/
query_manager.rs

1use crate::internals::{
2    context::context_manager::Track, parsing::deserialize, search::search_manager::SearchItem,
3};
4use anyhow::{Context, bail};
5use spotify_rs::model::PlayableItem;
6
7#[derive(Debug, Clone)]
8pub struct QueryManager {
9    pub playlist_url: String,
10    pub search_timeout_secs: u8,
11    client_id: Option<String>,
12    client_secret: Option<String>,
13}
14
15impl QueryManager {
16    pub fn new(
17        playlist_url: impl Into<String>,
18        client_id: Option<String>,
19        client_secret: Option<String>,
20    ) -> Self {
21        Self::new_with_timeout(playlist_url, client_id, client_secret, 10)
22    }
23
24    pub fn new_with_timeout(
25        playlist_url: impl Into<String>,
26        client_id: Option<String>,
27        client_secret: Option<String>,
28        search_timeout_secs: u8,
29    ) -> Self {
30        let playlist_url = playlist_url.into();
31        QueryManager {
32            playlist_url,
33            search_timeout_secs,
34            client_id,
35            client_secret,
36        }
37    }
38    pub async fn fetch_playlist(self) -> anyhow::Result<Vec<Track>> {
39        let Some(client_id) = self.client_id else {
40            bail!("CLIENT_ID is required to fetch Spotify playlist");
41        };
42        let Some(client_secret) = self.client_secret else {
43            bail!("CLIENT_SECRET is required to fetch Spotify playlist");
44        };
45        let spotify = spotify_rs::ClientCredsClient::authenticate(client_id, client_secret)
46            .await
47            .context("Authenticate Spotify client credentials")?;
48
49        let playlist = spotify_rs::playlist(self.playlist_url)
50            .market("US")
51            .get(&spotify)
52            .await
53            .context("Fetch Spotify playlist")?;
54        let pl = playlist
55            .tracks
56            .get_all(&spotify)
57            .await
58            .context("Paginating")?
59            .into_iter()
60            .flatten()
61            .flat_map(|track| {
62                if let PlayableItem::Track(song) = track.track {
63                    let Some(artist) = song.artists.first() else {
64                        tracing::warn!(track = song.name, "Skipping Spotify track without artist");
65                        return None;
66                    };
67                    Some(Track::Query(SearchItem::new(
68                        song.id,
69                        song.name,
70                        song.album.name,
71                        artist.name.clone(),
72                    )))
73                } else {
74                    None
75                }
76            })
77            .collect::<Vec<_>>();
78        tracing::info!(track_count = pl.len(), "Fetched Spotify playlist");
79        Ok(pl)
80    }
81    pub async fn run(&self) -> anyhow::Result<Vec<Track>> {
82        let data_string = include_str!("../parsing/sample.json");
83        let data: deserialize::Playlist =
84            serde_json::from_str(data_string).context("Deserializing")?;
85        let queries: Vec<SearchItem> = data.into();
86        let vals = queries.into_iter().map(Track::Query).collect();
87        Ok(vals)
88    }
89}