1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
// SPDX-License-Identifier: AGPL-3.0-only
use std::sync::Arc;
use std::time::Duration;
use std::sync::LazyLock;
use tokio::sync::RwLock;
use reqwest::{StatusCode, Client};
use urlencoding::encode as urlencode;
use super::{CacheFuture, CacheGetter, Cache, AsyncCache};
use crate::deserialize::{User, Track, TrackStub};
use crate::CONFIG;
pub type UserInfo = Arc<(User, Track, TrackStub)>;
type UserFuture = CacheFuture<Arc<(User, Track, TrackStub)>>;
type UserGetter = CacheGetter<Arc<(User, Track, TrackStub)>>;
type UserCache = Cache<Arc<(User, Track, TrackStub)>>;
#[derive(Debug)]
enum Allowlist {
Exclusive{cache: UserCache},
Open{default_cache: UserCache, allowlist_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::<GetUserInfo>(&userstr)
.map_err(|e| {log::error!("Couldn't parse user.getInfo for `{username}`: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't parse user.getInfo!")})?.user;
log::trace!("Parsed into: {userinfo:?}");
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::<GetRecentTracks>(&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!"))?;
log::trace!("Parsed into: {trackstub:?}");
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, urlencode(&trackstub.name), urlencode(&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 track.getInfo JSON for `{username}`: {trackstr}");
let trackinfo = serde_json::from_str::<GetTrackInfo>(&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;
log::trace!("Parsed into: {trackinfo:?}");
Ok(Arc::new((userinfo, trackinfo, trackstub)))
})
}
static HTTP: LazyLock<Client> = crate::http::lazy();
static ALLOWLIST: LazyLock<Allowlist> = LazyLock::new(|| {
let default_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.default_refresh, user_getter as UserGetter)));
let allowlist_cache = Arc::new(RwLock::new(AsyncCache::new(CONFIG.allowlist_refresh, user_getter as UserGetter)));
match CONFIG.allowlist_mode.as_str() {
"open" => {
Allowlist::Open{default_cache, allowlist_cache}
},
"exclusive" => {
if CONFIG.allowlist.is_empty() {
panic!("Exclusive mode set with empty allowlist, cannot serve any requests!");
}
Allowlist::Exclusive{cache: allowlist_cache}
},
m => {
panic!("Bad allowlist mode: `{m}`");
}
}
});
pub async fn get_userinfo(user: &String) -> (Result<Arc<(User, Track, TrackStub)>, (StatusCode, &'static str)>, Duration) {
match LazyLock::force(&ALLOWLIST) {
Allowlist::Open{default_cache, allowlist_cache} => {
if CONFIG.allowlist.contains(user) {
(allowlist_cache.write().await.get_owned(user).await, CONFIG.allowlist_refresh)
}
else {
(default_cache.write().await.get_owned(user).await, CONFIG.default_refresh)
}
},
Allowlist::Exclusive{cache} => {
if CONFIG.allowlist.contains(user) {
(cache.write().await.get_owned(user).await, CONFIG.allowlist_refresh)
}
else { (Err((StatusCode::FORBIDDEN, "User not in allowlist!")), CONFIG.default_refresh) }
}
}
}
pub fn touch() {
LazyLock::force(&ALLOWLIST);
}
|