mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-18 16:28:55 +00:00
Move to std::time::Duration
... to allow serde on all youmubot-osu structs
This commit is contained in:
parent
3af0b56755
commit
c7da9526e6
8 changed files with 202 additions and 88 deletions
|
@ -10,12 +10,23 @@ use request::builders::*;
|
|||
use request::*;
|
||||
use reqwest::Client as HTTPClient;
|
||||
use serenity::framework::standard::CommandError as Error;
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Client is the client that will perform calls to the osu! api server.
|
||||
pub struct Client {
|
||||
key: String,
|
||||
}
|
||||
|
||||
fn vec_try_into<U, T: std::convert::TryFrom<U>>(v: Vec<U>) -> Result<Vec<T>, T::Error> {
|
||||
let mut res = Vec::with_capacity(v.len());
|
||||
|
||||
for u in v.into_iter() {
|
||||
res.push(u.try_into()?);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client from the given API key.
|
||||
pub fn new(key: impl AsRef<str>) -> Client {
|
||||
|
@ -42,8 +53,8 @@ impl Client {
|
|||
) -> Result<Vec<Beatmap>, Error> {
|
||||
let mut r = BeatmapRequestBuilder::new(kind);
|
||||
f(&mut r);
|
||||
let res = self.build_request(client, r.build(client))?.json()?;
|
||||
Ok(res)
|
||||
let res: Vec<raw::Beatmap> = self.build_request(client, r.build(client))?.json()?;
|
||||
Ok(vec_try_into(res)?)
|
||||
}
|
||||
|
||||
pub fn user(
|
||||
|
@ -54,7 +65,8 @@ impl Client {
|
|||
) -> Result<Option<User>, Error> {
|
||||
let mut r = UserRequestBuilder::new(user);
|
||||
f(&mut r);
|
||||
let res: Vec<_> = self.build_request(client, r.build(client))?.json()?;
|
||||
let res: Vec<raw::User> = self.build_request(client, r.build(client))?.json()?;
|
||||
let res = vec_try_into(res)?;
|
||||
Ok(res.into_iter().next())
|
||||
}
|
||||
|
||||
|
@ -66,7 +78,8 @@ impl Client {
|
|||
) -> Result<Vec<Score>, Error> {
|
||||
let mut r = ScoreRequestBuilder::new(beatmap_id);
|
||||
f(&mut r);
|
||||
let mut res: Vec<Score> = self.build_request(client, r.build(client))?.json()?;
|
||||
let res: Vec<raw::Score> = self.build_request(client, r.build(client))?.json()?;
|
||||
let mut res: Vec<Score> = vec_try_into(res)?;
|
||||
|
||||
// with a scores request you need to fill the beatmap ids yourself
|
||||
res.iter_mut().for_each(|v| {
|
||||
|
@ -102,7 +115,8 @@ impl Client {
|
|||
) -> Result<Vec<Score>, Error> {
|
||||
let mut r = UserScoreRequestBuilder::new(u, user);
|
||||
f(&mut r);
|
||||
let res = self.build_request(client, r.build(client))?.json()?;
|
||||
let res: Vec<raw::Score> = self.build_request(client, r.build(client))?.json()?;
|
||||
let res = vec_try_into(res)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use chrono::{DateTime, Duration, Utc};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod deser;
|
||||
pub mod mods;
|
||||
pub mod parse;
|
||||
pub(crate) mod raw;
|
||||
|
||||
pub use mods::Mods;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub enum ApprovalStatus {
|
||||
Loved,
|
||||
Qualified,
|
||||
|
@ -28,7 +30,7 @@ impl fmt::Display for ApprovalStatus {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Difficulty {
|
||||
pub stars: f64,
|
||||
pub aim: Option<f64>,
|
||||
|
@ -45,7 +47,7 @@ pub struct Difficulty {
|
|||
pub max_combo: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
pub enum Genre {
|
||||
Any,
|
||||
Unspecified,
|
||||
|
@ -70,7 +72,7 @@ impl fmt::Display for Genre {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub enum Language {
|
||||
Any,
|
||||
Other,
|
||||
|
@ -92,7 +94,7 @@ impl fmt::Display for Language {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Mode {
|
||||
Std,
|
||||
Taiko,
|
||||
|
@ -116,7 +118,7 @@ impl fmt::Display for Mode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Beatmap {
|
||||
// Beatmapset info
|
||||
pub approval: ApprovalStatus,
|
||||
|
@ -169,7 +171,7 @@ impl Beatmap {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UserEvent {
|
||||
pub display_html: String,
|
||||
pub beatmap_id: u64,
|
||||
|
@ -178,7 +180,7 @@ pub struct UserEvent {
|
|||
pub epic_factor: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: u64,
|
||||
pub username: String,
|
||||
|
@ -216,7 +218,7 @@ impl User {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Rank {
|
||||
SS,
|
||||
SSH,
|
||||
|
@ -253,7 +255,7 @@ impl fmt::Display for Rank {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Score {
|
||||
pub id: Option<u64>, // No id if you fail
|
||||
pub user_id: u64,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// The mods available to osu!
|
||||
#[derive(std::default::Default)]
|
||||
#[derive(std::default::Default, Serialize, Deserialize)]
|
||||
pub struct Mods: u64 {
|
||||
const NOMOD = 0;
|
||||
const NF = 1 << 0;
|
||||
|
|
|
@ -1,17 +1,43 @@
|
|||
use super::*;
|
||||
use chrono::{
|
||||
format::{parse, Item, Numeric, Pad, Parsed},
|
||||
DateTime, Duration, Utc,
|
||||
DateTime, ParseError as ChronoParseError, Utc,
|
||||
};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use std::str::FromStr;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Duration;
|
||||
use std::{error::Error, fmt, str::FromStr};
|
||||
|
||||
impl<'de> Deserialize<'de> for Score {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw: raw::Score = raw::Score::deserialize(deserializer)?;
|
||||
/// Errors that can be identified from parsing.
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {
|
||||
InvalidValue { field: &'static str, value: String },
|
||||
FromStr(String),
|
||||
NoApprovalDate,
|
||||
DateParseError(ChronoParseError),
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use ParseError::*;
|
||||
match self {
|
||||
InvalidValue {
|
||||
ref field,
|
||||
ref value,
|
||||
} => write!(f, "Invalid value `{}` for {}", value, field),
|
||||
FromStr(ref s) => write!(f, "Invalid value `{}` parsing from string", s),
|
||||
NoApprovalDate => write!(f, "Approval date expected but not found"),
|
||||
DateParseError(ref r) => write!(f, "Error parsing date: {}", r),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
type ParseResult<T> = Result<T, ParseError>;
|
||||
|
||||
impl TryFrom<raw::Score> for Score {
|
||||
type Error = ParseError;
|
||||
fn try_from(raw: raw::Score) -> Result<Self, Self::Error> {
|
||||
Ok(Score {
|
||||
id: raw.score_id.map(parse_from_str).transpose()?,
|
||||
user_id: parse_from_str(&raw.user_id)?,
|
||||
|
@ -41,12 +67,9 @@ impl<'de> Deserialize<'de> for Score {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for User {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw: raw::User = raw::User::deserialize(deserializer)?;
|
||||
impl TryFrom<raw::User> for User {
|
||||
type Error = ParseError;
|
||||
fn try_from(raw: raw::User) -> Result<Self, Self::Error> {
|
||||
Ok(User {
|
||||
id: parse_from_str(&raw.user_id)?,
|
||||
username: raw.username,
|
||||
|
@ -80,12 +103,9 @@ impl<'de> Deserialize<'de> for User {
|
|||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
impl TryFrom<raw::Beatmap> for Beatmap {
|
||||
type Error = ParseError;
|
||||
fn try_from(raw: raw::Beatmap) -> Result<Self, Self::Error> {
|
||||
Ok(Beatmap {
|
||||
approval: parse_approval_status(&raw)?,
|
||||
submit_date: parse_date(&raw.submit_date)?,
|
||||
|
@ -129,7 +149,7 @@ impl<'de> Deserialize<'de> for Beatmap {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_user_event<E: de::Error>(s: raw::UserEvent) -> Result<UserEvent, E> {
|
||||
fn parse_user_event(s: raw::UserEvent) -> ParseResult<UserEvent> {
|
||||
Ok(UserEvent {
|
||||
display_html: s.display_html,
|
||||
beatmap_id: parse_from_str(&s.beatmap_id)?,
|
||||
|
@ -139,7 +159,7 @@ fn parse_user_event<E: de::Error>(s: raw::UserEvent) -> Result<UserEvent, E> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_mode<E: de::Error>(s: impl AsRef<str>) -> Result<Mode, E> {
|
||||
fn parse_mode(s: impl AsRef<str>) -> ParseResult<Mode> {
|
||||
let t: u8 = parse_from_str(s)?;
|
||||
use Mode::*;
|
||||
Ok(match t {
|
||||
|
@ -147,11 +167,16 @@ fn parse_mode<E: de::Error>(s: impl AsRef<str>) -> Result<Mode, E> {
|
|||
1 => Taiko,
|
||||
2 => Catch,
|
||||
3 => Mania,
|
||||
_ => return Err(E::custom(format!("invalid value {} for mode", t))),
|
||||
_ => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
field: "mode",
|
||||
value: t.to_string(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_language<E: de::Error>(s: impl AsRef<str>) -> Result<Language, E> {
|
||||
fn parse_language(s: impl AsRef<str>) -> ParseResult<Language> {
|
||||
let t: u8 = parse_from_str(s)?;
|
||||
use Language::*;
|
||||
Ok(match t {
|
||||
|
@ -167,11 +192,16 @@ fn parse_language<E: de::Error>(s: impl AsRef<str>) -> Result<Language, E> {
|
|||
9 => Swedish,
|
||||
10 => Spanish,
|
||||
11 => Italian,
|
||||
_ => return Err(E::custom(format!("invalid value {} for language", t))),
|
||||
_ => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
field: "langugae",
|
||||
value: t.to_string(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_genre<E: de::Error>(s: impl AsRef<str>) -> Result<Genre, E> {
|
||||
fn parse_genre(s: impl AsRef<str>) -> ParseResult<Genre> {
|
||||
let t: u8 = parse_from_str(s)?;
|
||||
use Genre::*;
|
||||
Ok(match t {
|
||||
|
@ -185,45 +215,57 @@ fn parse_genre<E: de::Error>(s: impl AsRef<str>) -> Result<Genre, E> {
|
|||
7 => Novelty,
|
||||
9 => HipHop,
|
||||
10 => Electronic,
|
||||
_ => return Err(E::custom(format!("invalid value {} for genre", t))),
|
||||
_ => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
field: "genre",
|
||||
value: t.to_string(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_duration<E: de::Error>(s: impl AsRef<str>) -> Result<Duration, E> {
|
||||
Ok(Duration::seconds(parse_from_str(s)?))
|
||||
fn parse_duration(s: impl AsRef<str>) -> ParseResult<Duration> {
|
||||
Ok(Duration::from_secs(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_from_str<T: FromStr>(s: impl AsRef<str>) -> ParseResult<T> {
|
||||
let v = s.as_ref();
|
||||
T::from_str(v).map_err(|_| ParseError::FromStr(v.to_owned()))
|
||||
}
|
||||
|
||||
fn parse_bool<E: de::Error>(b: impl AsRef<str>) -> Result<bool, E> {
|
||||
fn parse_bool(b: impl AsRef<str>) -> ParseResult<bool> {
|
||||
match b.as_ref() {
|
||||
"1" => Ok(true),
|
||||
"0" => Ok(false),
|
||||
_ => Err(E::custom("invalid value for bool")),
|
||||
t => Err(ParseError::InvalidValue {
|
||||
field: "bool",
|
||||
value: t.to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_approval_status<E: de::Error>(b: &raw::Beatmap) -> Result<ApprovalStatus, E> {
|
||||
fn parse_approval_status(b: &raw::Beatmap) -> ParseResult<ApprovalStatus> {
|
||||
use ApprovalStatus::*;
|
||||
Ok(match &b.approved[..] {
|
||||
"4" => Loved,
|
||||
"3" => Qualified,
|
||||
"2" => Approved,
|
||||
"1" => Ranked(parse_date(
|
||||
b.approved_date
|
||||
.as_ref()
|
||||
.ok_or(E::custom("expected approved date got none"))?,
|
||||
b.approved_date.as_ref().ok_or(ParseError::NoApprovalDate)?,
|
||||
)?),
|
||||
"0" => Pending,
|
||||
"-1" => WIP,
|
||||
"-2" => Graveyarded,
|
||||
_ => return Err(E::custom("invalid value for approval status")),
|
||||
t => {
|
||||
return Err(ParseError::InvalidValue {
|
||||
field: "approval status",
|
||||
value: t.to_owned(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_date<E: de::Error>(date: impl AsRef<str>) -> Result<DateTime<Utc>, E> {
|
||||
fn parse_date(date: impl AsRef<str>) -> ParseResult<DateTime<Utc>> {
|
||||
let mut parsed = Parsed::new();
|
||||
parse(
|
||||
&mut parsed,
|
||||
|
@ -243,6 +285,8 @@ fn parse_date<E: de::Error>(date: impl AsRef<str>) -> Result<DateTime<Utc>, E> {
|
|||
])
|
||||
.iter(),
|
||||
)
|
||||
.map_err(E::custom)?;
|
||||
parsed.to_datetime_with_timezone(&Utc {}).map_err(E::custom)
|
||||
.map_err(ParseError::DateParseError)?;
|
||||
parsed
|
||||
.to_datetime_with_timezone(&Utc {})
|
||||
.map_err(ParseError::DateParseError)
|
||||
}
|
|
@ -61,7 +61,7 @@ pub fn soft_ban(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResu
|
|||
msg.reply(&ctx, format!("⛓ Soft-banning user {}.", user.tag()))?;
|
||||
}
|
||||
Some(v) => {
|
||||
let until = Utc::now() + v.0;
|
||||
let until = Utc::now() + chrono::Duration::from_std(v.0)?;
|
||||
let until = server_ban
|
||||
.periodical_bans
|
||||
.entry(user.id)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
pub use duration::Duration;
|
||||
|
||||
mod duration {
|
||||
use chrono::Duration as StdDuration;
|
||||
use std::fmt;
|
||||
use std::time::Duration as StdDuration;
|
||||
use String as Error;
|
||||
// Parse a single duration unit
|
||||
fn parse_duration_string(s: &str) -> Result<StdDuration, Error> {
|
||||
|
@ -18,7 +18,7 @@ mod duration {
|
|||
.try_fold(
|
||||
ParseStep {
|
||||
current_value: None,
|
||||
current_duration: StdDuration::zero(),
|
||||
current_duration: StdDuration::from_secs(0),
|
||||
},
|
||||
|s, item| match (item, s.current_value) {
|
||||
('0'..='9', v) => Ok(ParseStep {
|
||||
|
@ -30,13 +30,13 @@ mod duration {
|
|||
current_value: None,
|
||||
current_duration: s.current_duration
|
||||
+ match item.to_ascii_lowercase() {
|
||||
's' => StdDuration::seconds,
|
||||
'm' => StdDuration::minutes,
|
||||
'h' => StdDuration::hours,
|
||||
'd' => StdDuration::days,
|
||||
'w' => StdDuration::weeks,
|
||||
's' => StdDuration::from_secs(1),
|
||||
'm' => StdDuration::from_secs(60),
|
||||
'h' => StdDuration::from_secs(60 * 60),
|
||||
'd' => StdDuration::from_secs(60 * 60 * 24),
|
||||
'w' => StdDuration::from_secs(60 * 60 * 24 * 7),
|
||||
_ => return Err(Error::from("Not a valid duration")),
|
||||
}(v as i64),
|
||||
} * (v as u32),
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
@ -65,9 +65,27 @@ mod duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl Duration {
|
||||
fn num_weeks(&self) -> u64 {
|
||||
self.0.as_secs() / (60 * 60 * 24 * 7)
|
||||
}
|
||||
fn num_days(&self) -> u64 {
|
||||
self.0.as_secs() / (60 * 60 * 24)
|
||||
}
|
||||
fn num_hours(&self) -> u64 {
|
||||
self.0.as_secs() / (60 * 60)
|
||||
}
|
||||
fn num_minutes(&self) -> u64 {
|
||||
self.0.as_secs() / 60
|
||||
}
|
||||
fn num_seconds(&self) -> u64 {
|
||||
self.0.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Duration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let d = &self.0;
|
||||
let d = self;
|
||||
// weeks
|
||||
let weeks = d.num_weeks();
|
||||
let days = d.num_days() - d.num_weeks() * 7;
|
||||
|
@ -103,17 +121,17 @@ mod duration {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::Duration as StdDuration;
|
||||
use std::time::Duration as StdDuration;
|
||||
#[test]
|
||||
fn test_parse_success() {
|
||||
let tests = [
|
||||
(
|
||||
"2D2h1m",
|
||||
StdDuration::seconds(2 * 60 * 60 * 24 + 2 * 60 * 60 + 1 * 60),
|
||||
StdDuration::from_secs(2 * 60 * 60 * 24 + 2 * 60 * 60 + 1 * 60),
|
||||
),
|
||||
(
|
||||
"1W2D3h4m5s",
|
||||
StdDuration::seconds(
|
||||
StdDuration::from_secs(
|
||||
1 * 7 * 24 * 60 * 60 + // 1W
|
||||
2 * 24 * 60 * 60 + // 2D
|
||||
3 * 60 * 60 + // 3h
|
||||
|
@ -123,7 +141,7 @@ mod duration {
|
|||
),
|
||||
(
|
||||
"1W2D3h4m5s6W",
|
||||
StdDuration::seconds(
|
||||
StdDuration::from_secs(
|
||||
1 * 7 * 24 * 60 * 60 + // 1W
|
||||
2 * 24 * 60 * 60 + // 2D
|
||||
3 * 60 * 60 + // 3h
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::commands::args::Duration as ParseDuration;
|
||||
use chrono::Duration;
|
||||
use serenity::framework::standard::CommandError as Error;
|
||||
use serenity::prelude::*;
|
||||
use serenity::{
|
||||
|
@ -12,6 +11,7 @@ use serenity::{
|
|||
};
|
||||
use std::collections::HashMap as Map;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[command]
|
||||
#[description = "🎌 Cast a poll upon everyone and ask them for opinions!"]
|
||||
|
@ -25,7 +25,7 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
let args = args.quoted();
|
||||
let _duration = args.single::<ParseDuration>()?;
|
||||
let duration = &_duration.0;
|
||||
if *duration < Duration::minutes(2) || *duration > Duration::days(1) {
|
||||
if *duration < Duration::from_secs(2 * 60) || *duration > Duration::from_secs(60 * 60 * 24) {
|
||||
msg.reply(ctx, format!("😒 Invalid duration ({}). The voting time should be between **2 minutes** and **1 day**.", _duration))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ pub fn vote(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
.try_for_each(|(v, _)| panel.react(&ctx, *v))?;
|
||||
|
||||
// Start sleeping
|
||||
thread::sleep(duration.to_std()?);
|
||||
thread::sleep(*duration);
|
||||
|
||||
let result = collect_reactions(ctx, &panel, &choices)?;
|
||||
if result.len() == 0 {
|
||||
|
|
|
@ -18,6 +18,7 @@ use youmubot_osu::{
|
|||
Client as OsuClient,
|
||||
};
|
||||
|
||||
mod cache;
|
||||
mod hook;
|
||||
|
||||
pub use hook::hook;
|
||||
|
@ -68,6 +69,25 @@ pub fn mania(ctx: &mut Context, msg: &Message, args: Args) -> CommandResult {
|
|||
get_user(ctx, msg, args, Mode::Mania)
|
||||
}
|
||||
|
||||
pub(crate) struct BeatmapWithMode(pub Beatmap, pub Mode);
|
||||
|
||||
impl BeatmapWithMode {
|
||||
/// Whether this beatmap-with-mode is a converted beatmap.
|
||||
fn is_converted(&self) -> bool {
|
||||
self.0.mode != self.1
|
||||
}
|
||||
|
||||
fn mode(&self) -> Mode {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Beatmap> for BeatmapWithMode {
|
||||
fn as_ref(&self) -> &Beatmap {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Save the given username as your username."]
|
||||
#[usage = "[username or user_id]"]
|
||||
|
@ -160,8 +180,6 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
}
|
||||
};
|
||||
|
||||
dbg!((nth, &mode, &user, &args));
|
||||
|
||||
let reqwest = data.get::<http::HTTP>().unwrap();
|
||||
let osu: &OsuClient = data.get::<http::Osu>().unwrap();
|
||||
let user = osu
|
||||
|
@ -180,6 +198,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
)?
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|v| BeatmapWithMode(v, mode))
|
||||
.unwrap();
|
||||
|
||||
msg.channel_id.send_message(&ctx, |m| {
|
||||
|
@ -187,7 +206,7 @@ pub fn recent(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult
|
|||
"{}: here is the play that you requested",
|
||||
msg.author
|
||||
))
|
||||
.embed(|m| score_embed(&recent_play, &beatmap, &user, &mode, None, m))
|
||||
.embed(|m| score_embed(&recent_play, &beatmap, &user, None, m))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
|
@ -222,8 +241,10 @@ fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> Command
|
|||
.into_iter()
|
||||
.next()
|
||||
.map(|m| {
|
||||
osu.beatmaps(reqwest, BeatmapRequestKind::Beatmap(m.beatmap_id), |f| f)
|
||||
.map(|map| (m, map.into_iter().next().unwrap()))
|
||||
osu.beatmaps(reqwest, BeatmapRequestKind::Beatmap(m.beatmap_id), |f| {
|
||||
f.mode(mode, true)
|
||||
})
|
||||
.map(|map| (m, BeatmapWithMode(map.into_iter().next().unwrap(), mode)))
|
||||
})
|
||||
.transpose()?;
|
||||
msg.channel_id.send_message(&ctx, |m| {
|
||||
|
@ -241,13 +262,14 @@ fn get_user(ctx: &mut Context, msg: &Message, args: Args, mode: Mode) -> Command
|
|||
|
||||
fn score_embed<'a>(
|
||||
s: &Score,
|
||||
b: &Beatmap,
|
||||
bm: &BeatmapWithMode,
|
||||
u: &User,
|
||||
mode: &Mode,
|
||||
top_record: Option<u8>,
|
||||
m: &'a mut CreateEmbed,
|
||||
) -> &'a mut CreateEmbed {
|
||||
let accuracy = s.accuracy(*mode);
|
||||
let mode = bm.mode();
|
||||
let b = &bm.0;
|
||||
let accuracy = s.accuracy(mode);
|
||||
let score_line = match &s.rank {
|
||||
Rank::SS | Rank::SSH => format!("SS"),
|
||||
_ if s.perfect => format!("{:2}% FC", accuracy),
|
||||
|
@ -293,7 +315,12 @@ fn score_embed<'a>(
|
|||
.push_bold(format!("{:.2}⭐", b.difficulty.stars))
|
||||
.push(", ")
|
||||
.push_bold_line(
|
||||
b.mode.to_string() + if b.mode == *mode { "" } else { " (Converted)" },
|
||||
b.mode.to_string()
|
||||
+ if bm.is_converted() {
|
||||
""
|
||||
} else {
|
||||
" (Converted)"
|
||||
},
|
||||
)
|
||||
.push("CS")
|
||||
.push_bold(format!("{:.1}", b.difficulty.cs))
|
||||
|
@ -313,7 +340,7 @@ fn score_embed<'a>(
|
|||
|
||||
fn user_embed<'a>(
|
||||
u: User,
|
||||
best: Option<(Score, Beatmap)>,
|
||||
best: Option<(Score, BeatmapWithMode)>,
|
||||
m: &'a mut CreateEmbed,
|
||||
) -> &'a mut CreateEmbed {
|
||||
m.title(u.username)
|
||||
|
@ -356,6 +383,7 @@ fn user_embed<'a>(
|
|||
false,
|
||||
)
|
||||
.fields(best.map(|(v, map)| {
|
||||
let map = map.0;
|
||||
(
|
||||
"Best Record",
|
||||
MessageBuilder::new()
|
||||
|
@ -364,7 +392,14 @@ fn user_embed<'a>(
|
|||
v.pp.unwrap() /*Top record should have pp*/
|
||||
))
|
||||
.push(" - ")
|
||||
.push_line(format!("{:.1} ago", Duration(Utc::now() - v.date)))
|
||||
.push_line(format!(
|
||||
"{:.1} ago",
|
||||
Duration(
|
||||
(Utc::now() - v.date)
|
||||
.to_std()
|
||||
.unwrap_or(std::time::Duration::from_secs(1))
|
||||
)
|
||||
))
|
||||
.push("on ")
|
||||
.push(format!(
|
||||
"[{} - {}]({})",
|
||||
|
|
Loading…
Add table
Reference in a new issue