From 7e168886558375c482f40554f1ea06015bb5cb2f Mon Sep 17 00:00:00 2001 From: Miwory Date: Wed, 26 Nov 2025 12:47:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=20get=5Fbeatmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/osuclient/api.py | 20 +++++++++++ src/osuclient/schema.py | 80 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index aa4de0b..f5729ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "osuclient" -version = "0.3.1" +version = "0.4.0" description = "Client for osu! API" readme = "README.md" authors = [ diff --git a/src/osuclient/api.py b/src/osuclient/api.py index 0600e82..f59f7ec 100644 --- a/src/osuclient/api.py +++ b/src/osuclient/api.py @@ -27,6 +27,26 @@ class osuAPIClient(AioHTTPXClient): logger='osu! API', ) + async def get_beatmap(self, access_token: str, beatmap: int): + req = await self.get( + f'/beatmaps/{beatmap}', + headers=self.clean_dict( + { + 'Authorization': f'Bearer {access_token}', + } + ), + ) + + match req.status_code: + case st.OK: + return s.BeatmapExtended.model_validate(req.json()) + + case st.NOT_FOUND: + return s.Error(404, 'Beatmap not found') + + case _: + return s.Error(500, 'Internal Server Error') + async def get_user_scores( self, access_token: str, diff --git a/src/osuclient/schema.py b/src/osuclient/schema.py index 66e5f7d..2a71b6d 100644 --- a/src/osuclient/schema.py +++ b/src/osuclient/schema.py @@ -182,6 +182,8 @@ class Team(BaseModel): class ScoreStatistics(BaseModel): + model_config = ConfigDict(extra='forbid') + count_100: int count_300: int count_50: int @@ -191,10 +193,14 @@ class ScoreStatistics(BaseModel): class CurrentUserAttributes(BaseModel): + model_config = ConfigDict(extra='forbid') + pin: None class Covers(BaseModel): + model_config = ConfigDict(extra='forbid') + cover: str cover_2x: str = Field(..., alias='cover@2x') card: str @@ -206,6 +212,8 @@ class Covers(BaseModel): class Beatmap(BaseModel): + model_config = ConfigDict(extra='forbid') + beatmapset_id: int difficulty_rating: float id: int @@ -236,6 +244,8 @@ class Beatmap(BaseModel): class Beatmapset(BaseModel): + model_config = ConfigDict(extra='forbid') + anime_cover: bool artist: str artist_unicode: str @@ -279,10 +289,80 @@ class User(BaseModel): class Weight(BaseModel): + model_config = ConfigDict(extra='forbid') + percentage: float pp: float +class BeatmapsetAvailability(BaseModel): + model_config = ConfigDict(extra='forbid') + + download_disabled: bool + more_information: str | None + + +class BeatmapsetNominationsSummaryRequiredMeta(BaseModel): + model_config = ConfigDict(extra='forbid') + + main_ruleset: int + non_main_ruleset: int + + +class BeatmapsetNominationsSummary(BaseModel): + model_config = ConfigDict(extra='forbid') + + current: int + eligible_main_rulesets: list[str] + required_meta: BeatmapsetNominationsSummaryRequiredMeta + + +class BeatmapsetExtended(Beatmapset): + model_config = ConfigDict(extra='forbid') + + bpm: float + can_be_hyped: bool + deleted_at: datetime | None + discussion_enabled: bool + discussion_locked: bool + is_scoreable: bool + last_updated: datetime + legacy_thread_url: str + ranked: int + ranked_date: datetime | None + rating: float + storyboard: bool + submitted_date: datetime + tags: str + ratings: list[int] + availability: BeatmapsetAvailability + nominations_summary: BeatmapsetNominationsSummary + + +class BeatmapOwner(BaseModel): + model_config = ConfigDict(extra='forbid') + + id: int + username: str + + +class BeatmapFailtimes(BaseModel): + model_config = ConfigDict(extra='forbid') + + fail: list[int] + exit: list[int] + + +class BeatmapExtended(Beatmap): + model_config = ConfigDict(extra='forbid') + + beatmapset: BeatmapsetExtended + max_combo: int + current_user_playcount: int + owners: list[BeatmapOwner] + failtimes: BeatmapFailtimes + + class Score(BaseModel): model_config = ConfigDict(extra='forbid')