use std::fmt;
use serde::{de, Deserialize};
use time::OffsetDateTime;
use crate::{Osu, OsuResult};
use super::{
beatmap::RankStatus,
serde_util,
user::{Medal, Username},
CacheUserFn, ContainedUsers, GameMode, Grade,
};
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct Events {
pub events: Vec<Event>,
#[serde(rename = "cursor_string", skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) sort: Option<EventSort>,
}
impl Events {
#[inline]
pub const fn has_more(&self) -> bool {
self.cursor.is_some()
}
pub async fn get_next(&self, osu: &Osu) -> Option<OsuResult<Events>> {
let cursor = self.cursor.as_deref()?;
let fut = osu
.events()
.cursor(cursor)
.sort(self.sort.unwrap_or_default());
Some(fut.await)
}
}
impl ContainedUsers for Events {
fn apply_to_users(&self, f: impl CacheUserFn) {
self.events.apply_to_users(f);
}
}
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct Event {
#[serde(with = "serde_util::datetime")]
pub created_at: OffsetDateTime,
#[serde(rename = "id")]
pub event_id: u32,
#[serde(flatten)]
pub event_type: EventType,
}
impl ContainedUsers for Event {
fn apply_to_users(&self, _: impl CacheUserFn) {}
}
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct EventBeatmap {
pub title: String,
pub url: String,
}
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct EventBeatmapset {
pub title: String,
pub url: String,
}
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum EventType {
BeatmapPlaycount { beatmap: EventBeatmap, count: u32 },
BeatmapsetApprove {
approval: RankStatus,
beatmapset: EventBeatmapset,
user: EventUser,
},
BeatmapsetDelete { beatmapset: EventBeatmapset },
BeatmapsetRevive {
beatmapset: EventBeatmapset,
user: EventUser,
},
BeatmapsetUpdate {
beatmapset: EventBeatmapset,
user: EventUser,
},
BeatmapsetUpload {
beatmapset: EventBeatmapset,
user: EventUser,
},
#[serde(rename = "achievement")]
Medal {
#[serde(rename = "achievement")]
medal: Medal,
user: EventUser,
},
Rank {
#[serde(rename = "scoreRank")]
grade: Grade,
rank: u32,
mode: GameMode,
beatmap: EventBeatmap,
user: EventUser,
},
RankLost {
mode: GameMode,
beatmap: EventBeatmap,
user: EventUser,
},
#[serde(rename = "userSupportAgain")]
SupportAgain { user: EventUser },
#[serde(rename = "userSupportFirst")]
SupportFirst { user: EventUser },
#[serde(rename = "userSupportGift")]
SupportGift {
user: EventUser,
},
UsernameChange {
user: EventUser,
},
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum EventSort {
#[default]
IdDescending,
IdAscending,
}
impl EventSort {
pub const fn as_str(&self) -> &'static str {
match self {
Self::IdDescending => "id_desc",
Self::IdAscending => "id_asc",
}
}
}
impl<'de> de::Deserialize<'de> for EventSort {
fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct EventSortVisitor;
impl de::Visitor<'_> for EventSortVisitor {
type Value = EventSort;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("id_desc or id_asc")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
match v {
"id_desc" => Ok(EventSort::IdDescending),
"id_asc" => Ok(EventSort::IdAscending),
_ => Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)),
}
}
}
d.deserialize_str(EventSortVisitor)
}
}
impl serde::Serialize for EventSort {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_str())
}
}
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct EventUser {
pub username: Username,
pub url: String,
#[serde(
default,
rename = "previousUsername",
skip_serializing_if = "Option::is_none"
)]
pub previous_username: Option<Username>,
}