Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,16 @@ impl Osu {
GetComments::new(self)
}

#[inline]
pub const fn changelog_build(&self, stream: String, build: String) -> GetChangelogBuild<'_> {
GetChangelogBuild::new(self, stream, build)
}

#[inline]
pub fn changelog_listing(&self) -> GetChangelogListing<'_> {
GetChangelogListing::new(self)
}

/// Get a [`ChartRankings`](crate::model::ranking::ChartRankings) struct
/// containing a [`Spotlight`](crate::model::ranking::Spotlight), its
/// [`BeatmapsetExtended`](crate::model::beatmap::BeatmapsetExtended)s, and participating
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ pub mod prelude {
error::OsuError,
model::{
beatmap::*,
changelog::*,
comments::*,
event::*,
forum::*,
Expand Down
124 changes: 124 additions & 0 deletions src/model/changelog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;

use super::serde_util;
use crate::model::ContainedUsers;

/// Changelog listing entry
#[derive(Clone, Debug, Deserialize)]
pub struct ChangelogListing {
Comment on lines +8 to +9
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should add the attribute #[cfg_attr(feature = "serialize", derive(serde::Serialize))] to this and other types

/// List of all game update streams (stable, lazer, etc)
pub streams: Vec<Stream>,
/// List of builds included
pub builds: Vec<Build>,
/// Search query inputs
pub search: Search,
}

impl ContainedUsers for ChangelogListing {
fn apply_to_users(&self, _f: impl super::CacheUserFn) {}
}

#[derive(Clone, Debug, Deserialize)]
pub struct Stream {
/// Build stream ID
pub id: i64,
/// Build stream title (stable40, lazer, etc)
pub name: String,
/// User-friendly build stream name
pub display_name: String,
/// Whether the build is displayed
pub is_featured: bool,
/// Latest deployed build information
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_build: Option<Box<Build>>,
/// Current live user count
#[serde(skip_serializing_if = "Option::is_none")]
pub user_count: Option<i64>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct Build {
/// Release build ID
pub id: i64,
/// Release build version
pub version: Option<String>,
/// User-friendly release build version
pub display_version: String,
/// Current live user count for the build
pub users: i64,
/// Build release date
#[serde(with = "serde_util::datetime")]
pub created_at: OffsetDateTime,
pub update_stream: Option<Stream>, // it is tagged as nullable but why would it be?
pub changelog_entries: Option<Vec<ChangelogEntry>>,
pub youtube_id: Option<String>,
/// Previous and next versions to this build
pub versions: Option<Versions>,
}

impl ContainedUsers for Build {
fn apply_to_users(&self, _f: impl super::CacheUserFn) {}
}

#[derive(Clone, Debug, Deserialize)]
pub struct Versions {
pub next: Option<Box<Build>>,
pub previous: Option<Box<Build>>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct ChangelogEntry {
pub id: Option<i64>,
pub repository: Option<String>,
pub github_pull_request_id: Option<i64>,
pub github_url: Option<String>,
/// Changelog entry URL in the news listing
pub url: Option<String>,
#[serde(rename = "type")]
/// Changelog entry type
pub entry_type: String, // TODO, technically defined but I can't read PHP
/// Changelog category
pub category: Option<String>, // TODO, technically defined but I can't read PHP
/// Changelog entry title
pub title: Option<String>,
#[cfg_attr(
feature = "serialize",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub message: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message_html: Option<String>,
pub major: bool,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "serde_util::option_datetime"
)]
pub created_at: Option<OffsetDateTime>,
pub github_user: GithubUser,
}

/// Github user behind the specific change
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GithubUser {
/// Display name of the user, may differ from github
pub display_name: String,
/// Github profile URL
pub github_url: Option<String>,
/// Github username
pub github_username: Option<String>,
pub id: Option<i64>,
pub osu_username: Option<String>,
pub user_id: Option<i64>,
pub user_url: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Search {
pub stream: Option<String>,
pub from: Option<String>,
pub to: Option<String>,
pub max_id: Option<i64>,
pub limit: i64,
}
1 change: 1 addition & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ pub mod seasonal_backgrounds;
/// User related types
pub mod user;

pub mod changelog;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module is public, a simple comment for documentation would be nice even if it's trivial

/// Wiki related types
pub mod wiki;

Expand Down
98 changes: 98 additions & 0 deletions src/request/changelog.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::{
prelude::{Build, ChangelogListing},
request::{Query, Request},
routing::Route,
Osu,
};

use serde::Serialize;

#[must_use = "requests must be configured and executed"]
#[derive(Serialize)]
pub struct GetChangelogBuild<'a> {
#[serde(skip)]
osu: &'a Osu,
stream: String,
build: String,
}

impl<'a> GetChangelogBuild<'a> {
pub(crate) const fn new(osu: &'a Osu, stream: String, build: String) -> Self {
Self { osu, stream, build }
}
}

into_future! {
|self: GetChangelogBuild<'_>| -> Build {
let route = Route::GetChangelogBuild {
stream: self.stream,
build: self.build,
};

Request::new(route)
}
}

#[must_use = "requests must be configured and executed"]
#[derive(Serialize)]
pub struct GetChangelogListing<'a> {
#[serde(skip)]
osu: &'a Osu,
from: Option<&'a str>,
to: Option<&'a str>,
max_id: Option<&'a str>,
stream: Option<&'a str>,
#[serde(skip_serializing_if = "Vec::is_empty")]
message_formats: Vec<&'a str>,
}

impl<'a> GetChangelogListing<'a> {
pub(crate) fn new(osu: &'a Osu) -> Self {
Self {
osu,
from: None,
to: None,
max_id: None,
// There are only two supported formats, it should be fine
message_formats: Vec::with_capacity(2).into(),
Comment on lines +56 to +57
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never push into the Vec, its setter overwrites it. Vec::new should be sufficient here.

stream: None,
}
}

/// Specify minimum build version
#[inline]
pub fn from(mut self, from: &'a str) -> Self {
self.from = Some(from);

self
}

/// Specify maximum build version
pub fn to(mut self, to: &'a str) -> Self {
self.to = Some(to);

self
}

/// Specify the release stream
pub fn stream(mut self, stream: &'a str) -> Self {
self.stream = Some(stream);

self
}

pub fn message_formats<I>(mut self, message_formats: I) -> Self
where
I: IntoIterator<Item = &'a str>,
{
self.message_formats = message_formats.into_iter().collect();

self
}
}

into_future! {
|self: GetChangelogListing<'_>| -> ChangelogListing {
Request::with_query(Route::GetChangelogListing, Query::encode(&self))
}
}
5 changes: 3 additions & 2 deletions src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ use crate::routing::Route;
pub use crate::future::OsuFuture;

pub use self::{
beatmap::*, comments::*, event::*, forum::*, matches::*, news::*, ranking::*, replay::*,
score::*, seasonal_backgrounds::*, user::*, wiki::*,
beatmap::*, changelog::*, comments::*, event::*, forum::*, matches::*, news::*, ranking::*,
replay::*, score::*, seasonal_backgrounds::*, user::*, wiki::*,
};

mod beatmap;
mod changelog;
mod comments;
mod event;
mod forum;
Expand Down
11 changes: 11 additions & 0 deletions src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ pub(crate) enum Route {
GetBeatmapsetFromMapId,
GetBeatmapsetEvents,
GetBeatmapsetSearch,
GetChangelogBuild {
stream: String,
build: String,
},
GetChangelogListing,
GetComments,
GetEvents,
GetForumPosts {
Expand Down Expand Up @@ -112,6 +117,10 @@ impl Route {
Self::GetBeatmapsetFromMapId => (Method::Get, "beatmapsets/lookup".into()),
Self::GetBeatmapsetEvents => (Method::Get, "beatmapsets/events".into()),
Self::GetBeatmapsetSearch => (Method::Get, "beatmapsets/search".into()),
Self::GetChangelogBuild { stream, build } => {
(Method::Get, format!("changelog/{stream}/{build}").into())
}
Self::GetChangelogListing => (Method::Get, "changelog".into()),
Self::GetComments => (Method::Get, "comments".into()),
Self::GetEvents => (Method::Get, "events".into()),
Self::GetForumPosts { topic_id } => {
Expand Down Expand Up @@ -216,6 +225,8 @@ impl Route {
Self::GetBeatmapsetFromMapId => "GetBeatmapsetFromMapId",
Self::GetBeatmapsetEvents => "GetBeatmapsetEvents",
Self::GetBeatmapsetSearch => "GetBeatmapsetSearch",
Self::GetChangelogBuild { .. } => "GetChangelogBuild",
Self::GetChangelogListing => "GetChangelogListing",
Self::GetComments => "GetComments",
Self::GetEvents => "GetEvents",
Self::GetForumPosts { .. } => "GetForumPosts",
Expand Down
9 changes: 9 additions & 0 deletions tests/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,3 +657,12 @@ async fn wiki() -> Result<()> {

Ok(())
}

#[tokio::test]
async fn changelogs() -> Result<()> {
let changelog = OSU.get().await?.changelog_listing().await?;

println!("Received {} changelog listings", changelog.builds.len());

Ok(())
}
Loading