use std::sync::LazyLock;
use reqwest::{Client, Error as reqwest_Error};
use serde::Deserialize;
use url::Url;
use crate::net::Request;
use crate::songdef::SongDifficulty;
static API: LazyLock<Url> = LazyLock::new(|| Url::parse("https://beatsaver.com/api/").expect("Invalid url"));
pub struct BeatSaverSearchRequest {
query: String,
order: String,
ascending: bool,
}
impl BeatSaverSearchRequest {
pub fn new<S: AsRef<str>>(query: S, order: S, ascending: bool) -> Self { Self {
query: query.as_ref().to_string(),
order: order.as_ref().to_string(),
ascending,
}
}
}
impl Request for BeatSaverSearchRequest {
type Response = BeatSaverSearchResponse;
type Error = reqwest_Error;
async fn exec(self, client: Client) -> Result<Self::Response, Self::Error> {
let url = API.join("search/text/0").expect("Invalid url");
client.get(url)
.query(&[
("q", self.query.as_str()),
("order", self.order.as_str()),
("ascending", if self.ascending { "true" } else { "false" }),
("pageSize", "100"), ])
.send()
.await?
.error_for_status()?
.json()
.await
}
}
#[derive(Deserialize)]
pub struct BeatSaverSearchResponse {
docs: Vec<BeatSaverSong>,
}
impl BeatSaverSearchResponse {
pub fn get_songs(&self) -> &[BeatSaverSong] {
&self.docs
}
}
#[derive(Deserialize)]
pub struct BeatSaverSong {
name: String,
uploader: BeatSaverSongUploader,
metadata: BeatSaverSongMetadata,
stats: BeatSaverSongStats,
versions: Vec<BeatSaverSongVersion>,
}
impl BeatSaverSong {
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_uploader(&self) -> &BeatSaverSongUploader {
&self.uploader
}
pub fn get_metadata(&self) -> &BeatSaverSongMetadata {
&self.metadata
}
pub fn get_stats(&self) -> &BeatSaverSongStats {
&self.stats
}
pub fn get_published_version(&self) -> Option<&BeatSaverSongVersion> {
self.versions.iter().find(|version| version.get_state() == "Published")
}
}
#[derive(Deserialize)]
pub struct BeatSaverSongUploader {
name: String,
}
impl BeatSaverSongUploader {
pub fn get_name(&self) -> &str {
&self.name
}
}
#[derive(Deserialize)]
pub struct BeatSaverSongMetadata {
bpm: f32, duration: i32, }
impl BeatSaverSongMetadata {
pub fn get_bpm(&self) -> f32 {
self.bpm
}
pub fn get_duration(&self) -> i32 {
self.duration
}
}
#[derive(Deserialize)]
pub struct BeatSaverSongStats {
score: f32, }
impl BeatSaverSongStats {
pub fn get_score(&self) -> f32 {
self.score
}
}
#[derive(Deserialize)]
pub struct BeatSaverSongVersion {
state: String,
#[serde(rename = "coverURL")]
cover_url: Url,
#[serde(rename = "previewURL")]
preview_url: Url,
#[serde(rename = "downloadURL")]
download_url: Url,
#[serde(rename = "diffs")]
variants: Vec<BeatSaverSongVariant>,
}
impl BeatSaverSongVersion {
pub fn get_state(&self) -> &str {
&self.state
}
pub fn get_cover_url(&self) -> &Url {
&self.cover_url
}
pub fn get_preview_url(&self) -> &Url {
&self.preview_url
}
pub fn get_download_url(&self) -> &Url {
&self.download_url
}
pub fn get_variants(&self) -> &[BeatSaverSongVariant] {
&self.variants
}
}
#[derive(Deserialize)]
pub struct BeatSaverSongVariant {
characteristic: String,
difficulty: SongDifficulty,
}
impl BeatSaverSongVariant {
pub fn get_characteristic(&self) -> &str {
&self.characteristic
}
pub fn get_difficulty(&self) -> SongDifficulty {
self.difficulty
}
}