mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-16 07:18:54 +00:00
Implement Beatmap request + parsing
This commit is contained in:
parent
efcf062e8f
commit
0cc9b318ad
7 changed files with 551 additions and 200 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1815,6 +1815,8 @@ name = "youmubot-osu"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serenity 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
|
|
@ -9,3 +9,5 @@ edition = "2018"
|
|||
[dependencies]
|
||||
serenity = "0.7"
|
||||
chrono = "0.4.10"
|
||||
reqwest = "0.9.24"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -1,206 +1,37 @@
|
|||
extern crate chrono;
|
||||
pub mod models;
|
||||
|
||||
pub mod models {
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
pub enum ApprovalStatus {
|
||||
Loved,
|
||||
Qualified,
|
||||
Approved,
|
||||
Ranked(DateTime<Utc>),
|
||||
Pending,
|
||||
WIP,
|
||||
Graveyarded,
|
||||
}
|
||||
pub struct Difficulty {
|
||||
pub stars: f64,
|
||||
pub aim: f64,
|
||||
pub speed: f64,
|
||||
pub mod request;
|
||||
|
||||
pub cs: f64,
|
||||
pub od: f64,
|
||||
pub ar: f64,
|
||||
pub hp: f64,
|
||||
use models::*;
|
||||
use request::builders::*;
|
||||
use request::*;
|
||||
use reqwest::Client as HTTPClient;
|
||||
use serenity::framework::standard::CommandError as Error;
|
||||
|
||||
pub count_normal: u64,
|
||||
pub count_slider: u64,
|
||||
pub count_spinner: u64,
|
||||
pub max_combo: u64,
|
||||
}
|
||||
pub enum Genre {
|
||||
Any,
|
||||
Unspecified,
|
||||
VideoGame,
|
||||
Anime,
|
||||
Rock,
|
||||
Pop,
|
||||
Other,
|
||||
Novelty,
|
||||
HipHop,
|
||||
Electronic,
|
||||
}
|
||||
pub enum Language {
|
||||
Any,
|
||||
Other,
|
||||
English,
|
||||
Japanese,
|
||||
Chinese,
|
||||
Instrumental,
|
||||
Korean,
|
||||
French,
|
||||
German,
|
||||
Swedish,
|
||||
Italian,
|
||||
}
|
||||
pub enum Mode {
|
||||
Std,
|
||||
Taiko,
|
||||
Mania,
|
||||
Catch,
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
||||
pub mod request {
|
||||
use super::Mode;
|
||||
use chrono::{DateTime, Utc};
|
||||
pub enum UserID {
|
||||
Username(String),
|
||||
ID(u64),
|
||||
Auto(String),
|
||||
}
|
||||
pub enum BeatmapRequestKind {
|
||||
User(UserID),
|
||||
Beatmap(u64),
|
||||
Beatmapset(u64),
|
||||
BeatmapHash(String),
|
||||
}
|
||||
pub struct BeatmapRequest {
|
||||
pub since: DateTime<Utc>,
|
||||
pub kind: BeatmapRequestKind,
|
||||
pub mode: Option<Mode>,
|
||||
pub converted: bool,
|
||||
}
|
||||
pub struct UserRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
pub event_days: Option<u8>,
|
||||
}
|
||||
pub struct ScoreRequest {
|
||||
pub beatmap_id: u64,
|
||||
pub user: Option<UserID>,
|
||||
pub mode: Option<Mode>,
|
||||
pub mods: u64, // Later
|
||||
}
|
||||
pub struct UserBestRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
||||
pub struct UserRecentRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
||||
}
|
||||
/// Client is the client that will perform calls to the osu! api server.
|
||||
pub struct Client {
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test() {}
|
||||
impl Client {
|
||||
/// Create a new client from the given API key.
|
||||
pub fn new(key: impl AsRef<str>) -> Client {
|
||||
Client {
|
||||
key: key.as_ref().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn beatmaps(
|
||||
&self,
|
||||
client: &HTTPClient,
|
||||
kind: BeatmapRequestKind,
|
||||
f: impl FnOnce(BeatmapRequestBuilder) -> BeatmapRequestBuilder,
|
||||
) -> Result<Vec<Beatmap>, Error> {
|
||||
let res = f(BeatmapRequestBuilder::new(kind))
|
||||
.build(client)
|
||||
.query(&[("k", &self.key)])
|
||||
.send()?
|
||||
.json()?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
160
youmubot-osu/src/models/deser.rs
Normal file
160
youmubot-osu/src/models/deser.rs
Normal 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)
|
||||
}
|
177
youmubot-osu/src/models/mod.rs
Normal file
177
youmubot-osu/src/models/mod.rs
Normal 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,
|
||||
}
|
42
youmubot-osu/src/models/raw.rs
Normal file
42
youmubot-osu/src/models/raw.rs
Normal 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,
|
||||
}
|
137
youmubot-osu/src/request.rs
Normal file
137
youmubot-osu/src/request.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use crate::models::Mode;
|
||||
use chrono::{DateTime, Utc};
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
|
||||
trait ToQuery {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)>;
|
||||
}
|
||||
|
||||
impl<T: ToQuery> ToQuery for Option<T> {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
match self {
|
||||
Some(ref v) => v.to_query(),
|
||||
None => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToQuery for Mode {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
vec![("m", (*self as u8).to_string())]
|
||||
}
|
||||
}
|
||||
|
||||
impl ToQuery for (&'static str, String) {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
vec![(self.0, self.1.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
impl ToQuery for (&'static str, DateTime<Utc>) {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
vec![(self.0, format!("{}", self.1.date().format("%Y-%m-%d")))]
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UserID {
|
||||
Username(String),
|
||||
ID(u64),
|
||||
Auto(String),
|
||||
}
|
||||
|
||||
impl ToQuery for UserID {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
use UserID::*;
|
||||
match self {
|
||||
Username(ref s) => vec![("u", s.clone()), ("type", "string".to_owned())],
|
||||
ID(u) => vec![("u", u.to_string()), ("type", "id".to_owned())],
|
||||
Auto(ref s) => vec![("u", s.clone())],
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum BeatmapRequestKind {
|
||||
ByUser(UserID),
|
||||
Beatmap(u64),
|
||||
Beatmapset(u64),
|
||||
BeatmapHash(String),
|
||||
}
|
||||
|
||||
impl ToQuery for BeatmapRequestKind {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
use BeatmapRequestKind::*;
|
||||
match self {
|
||||
ByUser(ref u) => u.to_query(),
|
||||
Beatmap(b) => vec![("b", b.to_string())],
|
||||
Beatmapset(s) => vec![("s", s.to_string())],
|
||||
BeatmapHash(ref h) => vec![("h", h.clone())],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod builders {
|
||||
use super::*;
|
||||
/// A builder for a Beatmap request.
|
||||
pub struct BeatmapRequestBuilder {
|
||||
kind: BeatmapRequestKind,
|
||||
since: Option<DateTime<Utc>>,
|
||||
mode: Mode,
|
||||
converted: bool,
|
||||
}
|
||||
impl BeatmapRequestBuilder {
|
||||
pub(crate) fn new(kind: BeatmapRequestKind) -> Self {
|
||||
BeatmapRequestBuilder {
|
||||
kind,
|
||||
since: None,
|
||||
mode: Mode::Std,
|
||||
converted: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn since(&mut self, since: DateTime<Utc>) -> &Self {
|
||||
self.since = Some(since);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(&mut self, mode: Mode, converted: bool) -> &Self {
|
||||
self.mode = mode;
|
||||
self.converted = converted;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build(self, client: &Client) -> RequestBuilder {
|
||||
client
|
||||
.get("https://osu.ppy.sh/api/get_beatmaps")
|
||||
.query(&self.kind.to_query())
|
||||
.query(&self.since.map(|v| ("since", v)).to_query())
|
||||
.query(&self.mode.to_query())
|
||||
.query(
|
||||
&(if self.converted {
|
||||
Some(("a", "1".to_owned()))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.to_query(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UserRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
pub event_days: Option<u8>,
|
||||
}
|
||||
pub struct ScoreRequest {
|
||||
pub beatmap_id: u64,
|
||||
pub user: Option<UserID>,
|
||||
pub mode: Option<Mode>,
|
||||
pub mods: u64, // Later
|
||||
}
|
||||
pub struct UserBestRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
||||
pub struct UserRecentRequest {
|
||||
pub user: UserID,
|
||||
pub mode: Option<Mode>,
|
||||
}
|
Loading…
Add table
Reference in a new issue