~aleteoryx/lfm_embed

ref: 94ecd2bba5fb5207d7ea241a8b5762bc2e1376f9 lfm_embed/src/cache/user.rs -rw-r--r-- 5.1 KiB
94ecd2bbalyx Add SPDX info to all files 7 months ago
                                                                                
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
// 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 super::{CacheFuture, CacheGetter, Cache, AsyncCache};
use crate::deserialize::{User, Track, TrackStub};
use crate::CONFIG;

type UserFuture = CacheFuture<Arc<(User, Track, TrackStub)>>;
type UserGetter = CacheGetter<Arc<(User, Track, TrackStub)>>;
type UserCache  = Cache<Arc<(User, Track, TrackStub)>>;

#[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::<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;

    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!"))?;

    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::<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;

    Ok(Arc::new((userinfo, trackinfo, trackstub)))
  })
}

static HTTP: LazyLock<Client> = crate::http::lazy();

static WHITELIST: LazyLock<Whitelist> = 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<Arc<(User, Track, TrackStub)>, (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) }
    }
  }
}