Skip to content

Commit

Permalink
Cheesegull Beatmapset API
Browse files Browse the repository at this point in the history
  • Loading branch information
cmyui committed Jun 22, 2024
1 parent 596c72d commit aea6c77
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 36 deletions.
117 changes: 116 additions & 1 deletion app/adapters/osu_api_v2.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from datetime import datetime
from enum import StrEnum
from typing import Any

import httpx
from pydantic import BaseModel
from pydantic import Field

from app import oauth
from app import settings


http_client = httpx.AsyncClient(
base_url="https://osu.ppy.sh/api/v2/",
auth=oauth.AsyncOAuth(
Expand Down Expand Up @@ -48,7 +51,8 @@ class Beatmap(BaseModel):

checksum: str | None
failtimes: Failtimes
max_combo: int
max_combo: int | None = None
bpm: int


class BeatmapExtended(Beatmap):
Expand Down Expand Up @@ -77,3 +81,114 @@ async def get_beatmap(beatmap_id: int) -> BeatmapExtended:
response = await http_client.get(f"beatmaps/{beatmap_id}")
response.raise_for_status()
return BeatmapExtended(**response.json())


class Covers(BaseModel):
# cover string
# cover@2x string
# card string
# card@2x string
# list string
# list@2x string
# slimcover string
# slimcover@2x string
cover: str
cover2x: str = Field(alias="cover@2x")
card: str
card2x: str = Field(alias="card@2x")
list: str
list2x: str = Field(alias="list@2x")
slimcover: str
slimcover2x: str = Field(alias="slimcover@2x")


class RequiredMeta(BaseModel):
main_ruleset: int
non_main_ruleset: int


class NominationsSummary(BaseModel):
current: int
eligible_main_rulesets: list[Ruleset]
required_meta: RequiredMeta


class Availability(BaseModel):
download_disabled: bool
more_information: str | None


class Genre(BaseModel):
id: int
name: str


class Description(BaseModel):
description: str # (html string)


class Language(BaseModel):
id: int
name: str


class Beatmapset(BaseModel):
artist: str
artist_unicode: str | None
covers: Covers
creator: str
favourite_count: int
hype: Any | None # TODO
id: int
nsfw: bool
offset: int
play_count: int
preview_url: str
source: str
spotlight: bool
status: str # TODO enum
title: str
title_unicode: str | None
user_id: int
video: bool

bpm: int | None
can_be_hyped: bool
deleted_at: datetime | None
discussion_enabled: bool
discussion_locked: bool
is_scoreable: bool
last_updated: datetime
legacy_thread_url: str
nominations_summary: NominationsSummary
ranked: int # TODO: enum
ranked_date: datetime | None
storyboard: bool
submitted_date: datetime
tags: str

availability: Availability

beatmaps: list[BeatmapExtended] | None
converts: list[BeatmapExtended]
current_nominations: list[str] | None
current_user_attributes: Any | None = None # TODO
description: Description
discussions: Any = None # TODO
events: Any | None = None # TODO
genre: Genre | None = None
has_favourited: Any = None # TODO
language: Language | None = None # TODO
pack_tags: list[str] | None
ratings: Any # TODO
recent_favourites: Any # TODO
related_users: Any # TODO
user: Any # TODO
track_id: int | None


async def get_beatmapset(beatmapset_id: int) -> Beatmapset:
response = await http_client.get(f"beatmapsets/{beatmapset_id}")
response.raise_for_status()
xd = response.json()
return Beatmapset(**xd)
119 changes: 84 additions & 35 deletions app/api/v1/cheesegull.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime

from fastapi import APIRouter
from pydantic import BaseModel

Expand All @@ -7,22 +9,6 @@


class CheesegullBeatmap(BaseModel):
# BeatmapID: 315,
# ParentSetID: 141,
# DiffName: "Insane",
# FileMD5: "1cf5b2c2edfafd055536d2cefcb89c0e",
# Mode: 0,
# BPM: 168,
# AR: 7,
# OD: 7,
# CS: 6,
# HP: 2,
# TotalLength: 14,
# HitLength: 14,
# Playcount: 1767740,
# Passcount: 1074297,
# MaxCombo: 114,
# DifficultyRating: 5.23
BeatmapID: int
ParentSetID: int
DiffName: str
Expand All @@ -40,31 +26,94 @@ class CheesegullBeatmap(BaseModel):
MaxCombo: int
DifficultyRating: float

@classmethod
def from_osu_api_beatmap(
cls,
beatmap: osu_api_v2.BeatmapExtended,
) -> "CheesegullBeatmap":
return cls(
BeatmapID=beatmap.id,
ParentSetID=beatmap.beatmapset_id,
DiffName=beatmap.version,
FileMD5=beatmap.checksum or "",
Mode=beatmap.mode_int,
BPM=beatmap.bpm or 0,
AR=beatmap.ar,
OD=beatmap.accuracy,
CS=beatmap.cs,
HP=beatmap.drain,
TotalLength=beatmap.total_length,
HitLength=beatmap.total_length,
Playcount=beatmap.playcount,
Passcount=beatmap.passcount,
MaxCombo=beatmap.max_combo or 0,
DifficultyRating=beatmap.difficulty_rating,
)


class CheesegullBeatmapset(BaseModel):
SetID: int
ChildrenBeatmaps: list[CheesegullBeatmap]
RankedStatus: int
ApprovedDate: datetime
LastUpdate: datetime
LastChecked: datetime
Artist: str
Title: str
Creator: str
Source: str
Tags: str
HasVideo: bool
Genre: int | None
Language: int | None
Favourites: int

@classmethod
def from_osu_api_beatmapset(
cls,
osu_api_beatmapset: osu_api_v2.Beatmapset,
) -> "CheesegullBeatmapset":
children_beatmaps: list[CheesegullBeatmap] = []
for osu_api_beatmap in osu_api_beatmapset.beatmaps or []:
if not isinstance(osu_api_beatmap, osu_api_v2.BeatmapExtended):
raise ValueError("beatmapset.beatmaps is not a list of BeatmapExtended")
cheesegull_beatmap = CheesegullBeatmap.from_osu_api_beatmap(osu_api_beatmap)
children_beatmaps.append(cheesegull_beatmap)

return cls(
SetID=osu_api_beatmapset.id,
ChildrenBeatmaps=children_beatmaps,
RankedStatus=osu_api_beatmapset.ranked,
ApprovedDate=osu_api_beatmapset.ranked_date or datetime.min,
LastUpdate=(
osu_api_beatmapset.ranked_date or osu_api_beatmapset.last_updated
),
LastChecked=datetime.now(), # TODO: Implement this
Artist=osu_api_beatmapset.artist,
Title=osu_api_beatmapset.title,
Creator=osu_api_beatmapset.creator,
Source=osu_api_beatmapset.source,
Tags=osu_api_beatmapset.tags,
HasVideo=osu_api_beatmapset.video,
Genre=osu_api_beatmapset.genre.id if osu_api_beatmapset.genre else None,
Language=(
osu_api_beatmapset.language.id if osu_api_beatmapset.language else None
),
Favourites=osu_api_beatmapset.favourite_count,
)


@router.get("/api/v1/cheesegull/b/{beatmap_id}")
async def cheesegull_beatmap(beatmap_id: int):
osu_api_beatmap = await osu_api_v2.get_beatmap(beatmap_id)
cheesegull_beatmap = CheesegullBeatmap(
BeatmapID=osu_api_beatmap.id,
ParentSetID=osu_api_beatmap.beatmapset_id,
DiffName=osu_api_beatmap.version,
FileMD5=osu_api_beatmap.checksum or "",
Mode=osu_api_beatmap.mode_int,
BPM=osu_api_beatmap.bpm or 0,
AR=osu_api_beatmap.ar,
OD=osu_api_beatmap.accuracy,
CS=osu_api_beatmap.cs,
HP=osu_api_beatmap.drain,
TotalLength=osu_api_beatmap.total_length,
HitLength=osu_api_beatmap.total_length,
Playcount=osu_api_beatmap.playcount,
Passcount=osu_api_beatmap.passcount,
MaxCombo=osu_api_beatmap.max_combo,
DifficultyRating=osu_api_beatmap.difficulty_rating,
)
cheesegull_beatmap = CheesegullBeatmap.from_osu_api_beatmap(osu_api_beatmap)
return cheesegull_beatmap.model_dump()


@router.get("/api/v1/cheesegull/s/{beatmapset_id}")
async def cheesegull_beatmapset(beatmapset_id: int):
return {"beatmapset_id": beatmapset_id}
osu_api_beatmapset = await osu_api_v2.get_beatmapset(beatmapset_id)
cheesegull_beatmapset = CheesegullBeatmapset.from_osu_api_beatmapset(
osu_api_beatmapset,
)
return cheesegull_beatmapset.model_dump()

0 comments on commit aea6c77

Please sign in to comment.