use std::sync::Arc; use std::time::Duration; use std::sync::LazyLock; use tokio::sync::RwLock; use reqwest::{StatusCode, Client}; use super::{CacheFuture, CacheGetter, Cache, AsyncCache}; use crate::deserialize::{User, Track, TrackStub}; use crate::CONFIG; type UserFuture = CacheFuture>; type UserGetter = CacheGetter>; type UserCache = Cache>; #[derive(Debug)] enum Whitelist { Exclusive{cache: UserCache}, Open{default_cache: UserCache, whitelist_cache: UserCache} } fn user_getter(username: &String) -> UserFuture { use crate::deserialize::{GetUserInfo, GetRecentTracks, GetTrackInfo}; let username = urlencoding::encode(username.as_ref()).to_string(); Box::pin(async move { let userreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getInfo&format=json&user={username}&api_key={}", CONFIG.lastfm_api_key)) .send().await .map_err(|e| {log::error!("Failed to get info for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; if userreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } let userstr = userreq.text().await.unwrap(); log::trace!("Got user.getUserInfo JSON for `{username}`: {userstr}"); let userinfo = serde_json::from_str::(&userstr) .map_err(|e| {log::error!("Couldn't parse user.getInfo for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getInfo!")})?.user; let tracksreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=user.getRecentTracks&format=json&extended=1&limit=1&user={username}&api_key={}", CONFIG.lastfm_api_key)) .send().await .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; if tracksreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "User does not exist!")); } if tracksreq.status() == StatusCode::FORBIDDEN { return Err((StatusCode::FORBIDDEN, "You need to unprivate your song history!")); } let tracksstr = tracksreq.text().await.unwrap(); log::trace!("Got user.getRecentTracks JSON for `{username}`: {tracksstr}"); let trackstub = serde_json::from_str::(&tracksstr) .map_err(|e| {log::error!("Couldn't parse user.getRecentTracks for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getRecentTracks!")})? .recenttracks.track.into_iter().next().ok_or((StatusCode::UNPROCESSABLE_ENTITY, "You need to listen to some songs first!"))?; let trackreq = HTTP.get(format!("https://ws.audioscrobbler.com/2.0/?method=track.getInfo&format=json&username={username}&api_key={}&track={}&artist={}", CONFIG.lastfm_api_key, trackstub.name, trackstub.artist.name)) .send().await .map_err(|e| {log::error!("Failed to get tracks for user `{username}`: {e}"); (StatusCode::SERVICE_UNAVAILABLE, "Couldn't connect to last.fm!")})?; if trackreq.status() == StatusCode::NOT_FOUND { return Err((StatusCode::NOT_FOUND, "Track does not exist!")); } let trackstr = trackreq.text().await.unwrap(); log::trace!("Got user.getTrackInfo JSON for `{username}`: {trackstr}"); let trackinfo = serde_json::from_str::(&trackstr) .map_err(|e| {log::error!("Couldn't parse track.getInfo for `{}` by `{}` on behalf of {username}: {e}", trackstub.name, trackstub.artist.name); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse track.getInfo!")})?.track; Ok(Arc::new((userinfo, trackinfo, trackstub))) }) } static HTTP: LazyLock = crate::http::lazy(); static WHITELIST: LazyLock = LazyLock::new(|| { let default_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.default_refresh, user_getter as UserGetter))); let whitelist_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.whitelist_refresh, user_getter as UserGetter))); match CONFIG.whitelist_mode.as_str() { "open" => { Whitelist::Open{default_cache, whitelist_cache} }, "exclusive" => { if CONFIG.whitelist.is_empty() { panic!("Exclusive mode set with empty whitelist, cannot serve any requests!"); } Whitelist::Exclusive{cache: whitelist_cache} }, m => { panic!("Bad whitelist mode: `{m}`"); } } }); pub async fn get_userinfo(user: &String) -> (Result, (StatusCode, &'static str)>, Duration) { match LazyLock::force(&WHITELIST) { Whitelist::Open{default_cache, whitelist_cache} => { if CONFIG.whitelist.contains(user) { (whitelist_cache.write().await.get_owned(user).await, CONFIG.whitelist_refresh) } else { (default_cache.write().await.get_owned(user).await, CONFIG.default_refresh) } }, Whitelist::Exclusive{cache} => { if CONFIG.whitelist.contains(user) { (cache.write().await.get_owned(user).await, CONFIG.whitelist_refresh) } else { (Err((StatusCode::FORBIDDEN, "User not in whitelist!")), CONFIG.default_refresh) } } } }