Implement Beatmap request + parsing

This commit is contained in:
Natsu Kagami 2019-12-23 17:33:46 -05:00
parent efcf062e8f
commit 0cc9b318ad
7 changed files with 551 additions and 200 deletions

View file

@ -0,0 +1,160 @@
use super::*;
use chrono::{
format::{parse, Item, Numeric, Pad, Parsed},
DateTime, Duration, Utc,
};
use serde::{de, Deserialize, Deserializer};
use std::str::FromStr;
impl<'de> Deserialize<'de> for Beatmap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw: raw::Beatmap = raw::Beatmap::deserialize(deserializer)?;
Ok(Beatmap {
approval: parse_approval_status(&raw)?,
submit_date: parse_date(&raw.submit_date)?,
last_update: parse_date(&raw.last_update)?,
download_available: !(parse_bool(&raw.download_unavailable)?),
audio_available: !(parse_bool(&raw.audio_unavailable)?),
artist: raw.artist,
beatmap_id: parse_from_str(&raw.beatmap_id)?,
beatmapset_id: parse_from_str(&raw.beatmapset_id)?,
title: raw.title,
bpm: parse_from_str(&raw.bpm)?,
creator: raw.creator,
creator_id: parse_from_str(&raw.creator_id)?,
source: raw.source,
genre: parse_genre(&raw.genre_id)?,
language: parse_language(&raw.language_id)?,
tags: raw.tags.split_whitespace().map(|v| v.to_owned()).collect(),
difficulty_name: raw.version,
difficulty: Difficulty {
stars: parse_from_str(&raw.difficultyrating)?,
aim: parse_from_str(&raw.diff_aim)?,
speed: parse_from_str(&raw.diff_speed)?,
cs: parse_from_str(&raw.diff_size)?,
od: parse_from_str(&raw.diff_overall)?,
ar: parse_from_str(&raw.diff_approach)?,
hp: parse_from_str(&raw.diff_drain)?,
count_normal: parse_from_str(&raw.count_normal)?,
count_slider: parse_from_str(&raw.count_slider)?,
count_spinner: parse_from_str(&raw.count_spinner)?,
max_combo: parse_from_str(&raw.max_combo)?,
},
drain_length: parse_duration(&raw.hit_length)?,
total_length: parse_duration(&raw.total_length)?,
file_hash: raw.file_md5,
mode: parse_mode(&raw.mode)?,
favourite_count: parse_from_str(&raw.favourite_count)?,
rating: parse_from_str(&raw.rating)?,
play_count: parse_from_str(&raw.playcount)?,
pass_count: parse_from_str(&raw.passcount)?,
})
}
}
fn parse_mode<E: de::Error>(s: impl AsRef<str>) -> Result<Mode, E> {
let t: u8 = parse_from_str(s)?;
use Mode::*;
Ok(match t {
0 => Std,
1 => Taiko,
2 => Mania,
3 => Catch,
_ => return Err(E::custom(format!("invalid value {} for mode", t))),
})
}
fn parse_language<E: de::Error>(s: impl AsRef<str>) -> Result<Language, E> {
let t: u8 = parse_from_str(s)?;
use Language::*;
Ok(match t {
0 => Any,
1 => Other,
2 => English,
3 => Japanese,
4 => Chinese,
5 => Instrumental,
6 => Korean,
7 => French,
8 => German,
9 => Swedish,
10 => Spanish,
11 => Italian,
_ => return Err(E::custom(format!("Invalid value {} for language", t))),
})
}
fn parse_genre<E: de::Error>(s: impl AsRef<str>) -> Result<Genre, E> {
let t: u8 = parse_from_str(s)?;
use Genre::*;
Ok(match t {
0 => Any,
1 => Unspecified,
2 => VideoGame,
3 => Anime,
4 => Rock,
5 => Pop,
6 => Other,
7 => Novelty,
9 => HipHop,
10 => Electronic,
_ => return Err(E::custom(format!("Invalid value {} for genre", t))),
})
}
fn parse_duration<E: de::Error>(s: impl AsRef<str>) -> Result<Duration, E> {
Ok(Duration::seconds(parse_from_str(s)?))
}
fn parse_from_str<T: FromStr, E: de::Error>(s: impl AsRef<str>) -> Result<T, E> {
T::from_str(s.as_ref()).map_err(|_| E::custom(format!("Invalid value {}", s.as_ref())))
}
fn parse_bool<E: de::Error>(b: impl AsRef<str>) -> Result<bool, E> {
match b.as_ref() {
"1" => Ok(true),
"0" => Ok(false),
_ => Err(E::custom("Invalid value for bool")),
}
}
fn parse_approval_status<E: de::Error>(b: &raw::Beatmap) -> Result<ApprovalStatus, E> {
use ApprovalStatus::*;
Ok(match &b.approved[..] {
"4" => Loved,
"3" => Qualified,
"2" => Approved,
"1" => Ranked(parse_date(&b.approved_date)?),
"0" => Pending,
"-1" => WIP,
"-2" => Graveyarded,
_ => return Err(E::custom("Invalid value for approval status")),
})
}
fn parse_date<E: de::Error>(date: impl AsRef<str>) -> Result<DateTime<Utc>, E> {
let mut parsed = Parsed::new();
parse(
&mut parsed,
date.as_ref(),
(&[
Item::Numeric(Numeric::Year, Pad::Zero),
Item::Literal("/"),
Item::Numeric(Numeric::Month, Pad::Zero),
Item::Literal("/"),
Item::Numeric(Numeric::Day, Pad::Zero),
Item::Space(""),
Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Literal("-"),
Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Literal("-"),
Item::Numeric(Numeric::Second, Pad::Zero),
])
.iter(),
)
.map_err(E::custom)?;
parsed.to_datetime_with_timezone(&Utc {}).map_err(E::custom)
}

View file

@ -0,0 +1,177 @@
use chrono::{DateTime, Duration, Utc};
use std::string::ToString;
pub mod deser;
pub(crate) mod raw;
#[derive(Debug)]
pub enum ApprovalStatus {
Loved,
Qualified,
Approved,
Ranked(DateTime<Utc>),
Pending,
WIP,
Graveyarded,
}
#[derive(Debug)]
pub struct Difficulty {
pub stars: f64,
pub aim: f64,
pub speed: f64,
pub cs: f64,
pub od: f64,
pub ar: f64,
pub hp: f64,
pub count_normal: u64,
pub count_slider: u64,
pub count_spinner: u64,
pub max_combo: u64,
}
#[derive(Debug)]
pub enum Genre {
Any,
Unspecified,
VideoGame,
Anime,
Rock,
Pop,
Other,
Novelty,
HipHop,
Electronic,
}
#[derive(Debug)]
pub enum Language {
Any,
Other,
English,
Japanese,
Chinese,
Instrumental,
Korean,
French,
German,
Swedish,
Spanish,
Italian,
}
#[derive(Clone, Copy, Debug)]
pub enum Mode {
Std,
Taiko,
Mania,
Catch,
}
impl ToString for Mode {
fn to_string(&self) -> String {
(*self as u64).to_string()
}
}
#[derive(Debug)]
pub struct Beatmap {
// Beatmapset info
pub approval: ApprovalStatus,
pub submit_date: DateTime<Utc>,
pub last_update: DateTime<Utc>,
pub download_available: bool,
pub audio_available: bool,
// Media metadata
pub artist: String,
pub title: String,
pub beatmapset_id: u64,
pub bpm: f64,
pub creator: String,
pub creator_id: u64,
pub source: Option<String>,
pub genre: Genre,
pub language: Language,
pub tags: Vec<String>,
// Beatmap information
pub beatmap_id: u64,
pub difficulty_name: String,
pub difficulty: Difficulty,
pub drain_length: Duration,
pub total_length: Duration,
pub file_hash: String,
pub mode: Mode,
pub favourite_count: u64,
pub rating: f64,
pub play_count: u64,
pub pass_count: u64,
}
pub struct UserEvent {
pub display_html: String,
pub beatmap_id: u64,
pub beatmapset_id: u64,
pub date: DateTime<Utc>,
pub epic_factor: u8,
}
pub struct User {
pub id: u64,
pub username: String,
pub joined: DateTime<Utc>,
pub country: String,
// History
pub count_300: u64,
pub count_100: u64,
pub count_50: u64,
pub play_count: u64,
pub played_time: Duration,
pub ranked_score: u64,
pub total_score: u64,
pub count_ss: u64,
pub count_ssh: u64,
pub count_s: u64,
pub count_sh: u64,
pub count_a: u64,
pub events: Vec<UserEvent>,
// Rankings
pub rank: u64,
pub level: f64,
pub pp: Option<u64>,
pub accuracy: f64,
}
pub enum Rank {
SS,
SSH,
S,
SH,
A,
B,
C,
D,
F,
}
pub struct Score {
pub id: u64,
pub username: String,
pub user_id: u64,
pub date: DateTime<Utc>,
pub replay_available: bool,
pub score: u64,
pub pp: f64,
pub rank: Rank,
pub mods: u64, // Later
pub count_300: u64,
pub count_100: u64,
pub count_50: u64,
pub count_miss: u64,
pub count_katu: u64,
pub count_geki: u64,
pub max_combo: u64,
pub perfect: bool,
}

View file

@ -0,0 +1,42 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct Beatmap {
pub approved: String,
pub submit_date: String,
pub approved_date: String,
pub last_update: String,
pub artist: String,
pub beatmap_id: String,
pub beatmapset_id: String,
pub bpm: String,
pub creator: String,
pub creator_id: String,
pub difficultyrating: String,
pub diff_aim: String,
pub diff_speed: String,
pub diff_size: String,
pub diff_overall: String,
pub diff_approach: String,
pub diff_drain: String,
pub hit_length: String,
pub source: Option<String>,
pub genre_id: String,
pub language_id: String,
pub title: String,
pub total_length: String,
pub version: String,
pub file_md5: String,
pub mode: String,
pub tags: String,
pub favourite_count: String,
pub rating: String,
pub playcount: String,
pub passcount: String,
pub count_normal: String,
pub count_slider: String,
pub count_spinner: String,
pub max_combo: String,
pub download_unavailable: String,
pub audio_unavailable: String,
}