Рефактор
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "osuclient"
|
name = "osuclient"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
description = "Client for osu! API"
|
description = "Client for osu! API"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@ -12,278 +12,34 @@ class osuAPIClient(AioHTTPXClient):
|
|||||||
redis_url: str,
|
redis_url: str,
|
||||||
client_id: str,
|
client_id: str,
|
||||||
client_secret: str,
|
client_secret: str,
|
||||||
callback_url: str,
|
redirect_uri: str,
|
||||||
):
|
):
|
||||||
self.base_uri = 'https://osu.ppy.sh/api/v2'
|
self.base_uri = 'https://osu.ppy.sh/api/v2'
|
||||||
self.client_id = client_id
|
self.client_id = client_id
|
||||||
self.client_secret = client_secret
|
self.client_secret = client_secret
|
||||||
self.callback_url = callback_url
|
self.redirect_uri = redirect_uri
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
base_url=self.base_uri,
|
base_url=self.base_uri,
|
||||||
redis_url=redis_url,
|
redis_url=redis_url,
|
||||||
key='osu',
|
key='osu',
|
||||||
limit=10,
|
limit=1200,
|
||||||
logger='osu! API',
|
logger='osu! API',
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_beatmap_favorites(
|
|
||||||
self, access_token: str, cache_time: int | None = None
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
'/me/beatmapset-favourites',
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.FavoriteBeatmaps.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def get_beatmap_packs(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
packs_type: Literal[
|
|
||||||
'standard',
|
|
||||||
'featured',
|
|
||||||
'tournament',
|
|
||||||
'loved',
|
|
||||||
'chart',
|
|
||||||
'theme',
|
|
||||||
'artist',
|
|
||||||
] = 'standard',
|
|
||||||
cursor_string: str | None = None,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
'/beatmaps/packs',
|
|
||||||
params=self.clean_dict(
|
|
||||||
{'type': packs_type, 'cursor_string': cursor_string}
|
|
||||||
),
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.BeatmapPacks.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def get_beatmap_pack(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
pack: str,
|
|
||||||
legacy_only: int = 0,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
f'/beatmaps/packs/{pack}',
|
|
||||||
params=self.clean_dict({'legacy_only': legacy_only}),
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.BeatmapPack.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case st.NOT_FOUND:
|
|
||||||
raise s.Error(req.status_code, 'Not Found')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def lookup_beatmap(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
checksum: str | None = None,
|
|
||||||
filename: str | None = None,
|
|
||||||
beatmap_id: int | None = None,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
'/beatmaps/lookup',
|
|
||||||
params=self.clean_dict(
|
|
||||||
{
|
|
||||||
'checksum': checksum,
|
|
||||||
'filename': filename,
|
|
||||||
'beatmap_id': beatmap_id,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.BeatmapExtended.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case st.NOT_FOUND:
|
|
||||||
raise s.Error(req.status_code, 'Not Found')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def get_user_beatmap_score(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
beatmap: int,
|
|
||||||
user: int,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
f'/beatmaps/{beatmap}/scores/users/{user}',
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.BeatmapUserScore.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case st.NOT_FOUND:
|
|
||||||
raise s.Error(req.status_code, 'Not Found')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def get_user_beatmap_scores(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
beatmap: int,
|
|
||||||
user: int,
|
|
||||||
legacy_only: int = 0,
|
|
||||||
ruleset: Literal['fruits', 'mania', 'osu', 'taiko'] | None = None,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
f'/beatmaps/{beatmap}/scores/users/{user}/all',
|
|
||||||
params=self.clean_dict(
|
|
||||||
{'legacy_only': legacy_only, 'ruleset': ruleset}
|
|
||||||
),
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.UserBeatmapScores.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case st.NOT_FOUND:
|
|
||||||
raise s.Error(req.status_code, 'Not Found')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
async def get_beatmap_scores(
|
|
||||||
self,
|
|
||||||
access_token: str,
|
|
||||||
beatmap: int,
|
|
||||||
legacy_only: int = 0,
|
|
||||||
mode: Literal['fruits', 'mania', 'osu', 'taiko'] | None = None,
|
|
||||||
mods: str | None = None,
|
|
||||||
ranking_type: str | None = None,
|
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
|
||||||
req = await self.get(
|
|
||||||
f'/beatmaps/{beatmap}/scores',
|
|
||||||
params=self.clean_dict(
|
|
||||||
{
|
|
||||||
'legacy_only': legacy_only,
|
|
||||||
'mode': mode,
|
|
||||||
'mods': mods,
|
|
||||||
'ranking_type': ranking_type,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
headers=self.clean_dict(
|
|
||||||
{
|
|
||||||
'Authorization': f'Bearer {access_token}',
|
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
match req.status_code:
|
|
||||||
case st.OK:
|
|
||||||
return s.BeatmapScores.model_validate(req.json())
|
|
||||||
|
|
||||||
case st.UNAUTHORIZED:
|
|
||||||
raise s.Error(req.status_code, 'Unauthorized')
|
|
||||||
|
|
||||||
case _:
|
|
||||||
self.logger.error(req.text)
|
|
||||||
raise s.Error(500, 'Internal Server Error')
|
|
||||||
|
|
||||||
# TODO: implement endpoints
|
|
||||||
# https://osu.ppy.sh/docs/index.html#get-beatmaps
|
|
||||||
|
|
||||||
async def get_user_scores(
|
async def get_user_scores(
|
||||||
self,
|
self,
|
||||||
access_token: str,
|
access_token: str,
|
||||||
user: int,
|
user_id: int,
|
||||||
score_type: Literal['best', 'firsts', 'recent'],
|
score_type: Literal['best', 'recent', 'firsts'],
|
||||||
legacy_only: int = 0,
|
legacy_only: Literal[0, 1] = 0,
|
||||||
include_fails: int = 0,
|
include_fails: Literal[0, 1] = 0,
|
||||||
mode: Literal['fruits', 'mania', 'osu', 'taiko'] | None = None,
|
mode: Literal['fruits', 'mania', 'osu', 'taiko'] | None = None,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
offset: int | None = None,
|
offset: str | None = None,
|
||||||
cache_time: int | None = None,
|
|
||||||
):
|
):
|
||||||
req = await self.get(
|
req = await self.get(
|
||||||
f'/users/{user}/scores/{score_type}',
|
f'/users/{user_id}/scores/{score_type}',
|
||||||
params=self.clean_dict(
|
params=self.clean_dict(
|
||||||
{
|
{
|
||||||
'legacy_only': legacy_only,
|
'legacy_only': legacy_only,
|
||||||
@ -296,7 +52,6 @@ class osuAPIClient(AioHTTPXClient):
|
|||||||
headers=self.clean_dict(
|
headers=self.clean_dict(
|
||||||
{
|
{
|
||||||
'Authorization': f'Bearer {access_token}',
|
'Authorization': f'Bearer {access_token}',
|
||||||
'X-Cache-TTL': cache_time,
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -315,9 +70,6 @@ class osuAPIClient(AioHTTPXClient):
|
|||||||
self.logger.error(req.text)
|
self.logger.error(req.text)
|
||||||
raise s.Error(500, 'Internal Server Error')
|
raise s.Error(500, 'Internal Server Error')
|
||||||
|
|
||||||
# TODO: implement other endpoints
|
|
||||||
# https://osu.ppy.sh/docs/index.html#get-user-beatmaps
|
|
||||||
|
|
||||||
async def get_user(
|
async def get_user(
|
||||||
self,
|
self,
|
||||||
access_token: str,
|
access_token: str,
|
||||||
@ -342,7 +94,7 @@ class osuAPIClient(AioHTTPXClient):
|
|||||||
|
|
||||||
match req.status_code:
|
match req.status_code:
|
||||||
case st.OK:
|
case st.OK:
|
||||||
return s.GetUserSchema.model_validate(req.json())
|
return s.GetUser.model_validate(req.json())
|
||||||
|
|
||||||
case st.NOT_FOUND:
|
case st.NOT_FOUND:
|
||||||
raise s.Error(req.status_code, 'Not Found')
|
raise s.Error(req.status_code, 'Not Found')
|
||||||
|
|||||||
@ -114,7 +114,7 @@ class osuAuthClient(AioHTTPXClient):
|
|||||||
return s.Token.model_validate(req.json())
|
return s.Token.model_validate(req.json())
|
||||||
|
|
||||||
case st.BAD_REQUEST:
|
case st.BAD_REQUEST:
|
||||||
raise s.Error(req.status_code, req.json()['error'])
|
raise s.Error(req.status_code, req.json()['message'])
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
raise s.Error(req.status_code, 'Internal Server Error')
|
raise s.Error(req.status_code, 'Internal Server Error')
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@ -25,432 +25,146 @@ class UserToken(Token):
|
|||||||
|
|
||||||
|
|
||||||
class Country(BaseModel):
|
class Country(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
code: str
|
code: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
class Beatmap(BaseModel):
|
class Cover(BaseModel):
|
||||||
beatmapset_id: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
difficulty_rating: float
|
|
||||||
id: int
|
custom_url: str
|
||||||
mode: Literal['fruits', 'mania', 'osu', 'taiko']
|
url: str
|
||||||
status: int
|
id: int | None
|
||||||
total_length: int
|
|
||||||
user_id: int
|
|
||||||
version: str
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapDifficultyAttributes(BaseModel):
|
class DailyChallengeUserStats(BaseModel):
|
||||||
star_rating: float
|
model_config = ConfigDict(extra='forbid')
|
||||||
max_combo: int
|
|
||||||
|
|
||||||
|
daily_streak_best: int
|
||||||
class osuBeatmapDifficultyAttributes(BeatmapDifficultyAttributes):
|
daily_streak_current: int
|
||||||
aim_difficulty: float
|
last_update: str
|
||||||
aim_difficult_slider_count: float
|
last_weekly_streak: str
|
||||||
speed_difficulty: float
|
|
||||||
speed_note_count: float
|
|
||||||
slider_factor: float
|
|
||||||
aim_difficult_strain_count: float
|
|
||||||
speed_difficult_strain_count: float
|
|
||||||
|
|
||||||
|
|
||||||
class taikoBeatmapDifficultyAttributes(BeatmapDifficultyAttributes):
|
|
||||||
mono_stamina_factor: float
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapExtended(Beatmap):
|
|
||||||
accuracy: float
|
|
||||||
ar: float
|
|
||||||
beatmapset_id: int
|
|
||||||
bpm: float | None = None
|
|
||||||
convert: bool
|
|
||||||
count_circles: int
|
|
||||||
count_sliders: int
|
|
||||||
count_spinners: int
|
|
||||||
cs: float
|
|
||||||
deleted_at: datetime | None = None
|
|
||||||
drain: float
|
|
||||||
hit_length: int
|
|
||||||
is_scoreable: bool
|
|
||||||
last_updated: datetime
|
|
||||||
mode_int: int
|
|
||||||
passcount: int
|
|
||||||
playcount: int
|
playcount: int
|
||||||
ranked: int
|
top_10p_placements: int
|
||||||
url: str
|
top_50p_placements: int
|
||||||
|
user_id: int
|
||||||
|
weekly_streak_best: int
|
||||||
|
weekly_streak_current: int
|
||||||
|
|
||||||
|
|
||||||
class BeatmapOwner(BaseModel):
|
class MonthlyPlaycount(BaseModel):
|
||||||
id: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
username: str
|
|
||||||
|
|
||||||
|
start_date: str
|
||||||
class UserCompletionData(BaseModel):
|
|
||||||
beatmapset_ids: list[int]
|
|
||||||
completed: bool
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapPack(BaseModel):
|
|
||||||
author: str
|
|
||||||
date: datetime
|
|
||||||
name: str
|
|
||||||
no_diff_reduction: bool
|
|
||||||
ruleset_id: int | None
|
|
||||||
tag: str
|
|
||||||
url: str
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapPlaycount(BaseModel):
|
|
||||||
beatmap_id: int
|
|
||||||
beatmap: 'Beatmap | None' = None
|
|
||||||
beatmapset: 'Beatmapset | None' = None
|
|
||||||
count: int
|
count: int
|
||||||
|
|
||||||
|
|
||||||
class BeatmapScores(BaseModel):
|
class Page(BaseModel):
|
||||||
scores: list['Score']
|
model_config = ConfigDict(extra='forbid')
|
||||||
userScore: 'BeatmapUserScore | None' = None
|
|
||||||
|
html: str
|
||||||
|
raw: str
|
||||||
|
|
||||||
|
|
||||||
class BeatmapUserScore(BaseModel):
|
class RankHighest(BaseModel):
|
||||||
position: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
score: 'Score'
|
|
||||||
|
rank: int
|
||||||
|
updated_at: str
|
||||||
|
|
||||||
|
|
||||||
class Beatmapset(BaseModel):
|
class Kudosu(BaseModel):
|
||||||
artist: str
|
model_config = ConfigDict(extra='forbid')
|
||||||
artist_unicode: str
|
|
||||||
covers: 'Covers'
|
available: int
|
||||||
creator: str
|
total: int
|
||||||
favourite_count: int
|
|
||||||
id: int
|
|
||||||
nsfw: bool
|
class ReplaysWatchedCount(BaseModel):
|
||||||
offset: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
start_date: str
|
||||||
|
count: int
|
||||||
|
|
||||||
|
|
||||||
|
class Level(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
current: int
|
||||||
|
progress: int
|
||||||
|
|
||||||
|
|
||||||
|
class GradeCounts(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
ss: int
|
||||||
|
ssh: int
|
||||||
|
s: int
|
||||||
|
sh: int
|
||||||
|
a: int
|
||||||
|
|
||||||
|
|
||||||
|
class Rank(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
country: int
|
||||||
|
|
||||||
|
|
||||||
|
class UserStatistics(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
count_100: int
|
||||||
|
count_300: int
|
||||||
|
count_50: int
|
||||||
|
count_miss: int
|
||||||
|
level: Level
|
||||||
|
global_rank: int
|
||||||
|
global_rank_percent: float
|
||||||
|
global_rank_exp: None
|
||||||
|
pp: float
|
||||||
|
pp_exp: int
|
||||||
|
ranked_score: int
|
||||||
|
hit_accuracy: float
|
||||||
play_count: int
|
play_count: int
|
||||||
preview_url: str
|
play_time: int
|
||||||
source: str
|
total_score: int
|
||||||
status: str
|
total_hits: int
|
||||||
spotlight: bool
|
maximum_combo: int
|
||||||
title: str
|
replays_watched_by_others: int
|
||||||
title_unicode: str
|
is_ranked: bool
|
||||||
user_id: int
|
grade_counts: GradeCounts
|
||||||
video: bool
|
country_rank: int
|
||||||
|
rank: Rank
|
||||||
|
|
||||||
|
|
||||||
class Covers(BaseModel):
|
class UserAchievement(BaseModel):
|
||||||
cover: str
|
model_config = ConfigDict(extra='forbid')
|
||||||
cover_2x: str = Field(alias='cover@2x')
|
|
||||||
card: str
|
|
||||||
card_2x: str = Field(alias='card@2x')
|
|
||||||
list: str
|
|
||||||
list_2x: str = Field(alias='list@2x')
|
|
||||||
slimcover: str
|
|
||||||
slimcover_2x: str = Field(alias='slimcover@2x')
|
|
||||||
|
|
||||||
|
achieved_at: str
|
||||||
|
achievement_id: int
|
||||||
|
|
||||||
class BeatmapsetDiscussion(BaseModel):
|
|
||||||
beatmap: 'Beatmap | None' = None
|
|
||||||
beatmap_id: int | None = None
|
|
||||||
beatmapset: 'Beatmapset | None' = None
|
|
||||||
beatmapset_id: int
|
|
||||||
can_be_resolved: bool
|
|
||||||
can_grant_kudosu: bool
|
|
||||||
created_at: datetime
|
|
||||||
current_user_attributes: 'CurrentUserAttributes'
|
|
||||||
deleted_at: datetime | None = None
|
|
||||||
deleted_by_id: int | None = None
|
|
||||||
id: int
|
|
||||||
kudosu_denied: bool
|
|
||||||
last_post_at: datetime
|
|
||||||
message_type: Literal[
|
|
||||||
'hype', 'mapper_note', 'praise', 'problem', 'review', 'suggestion'
|
|
||||||
]
|
|
||||||
parent_id: int | None = None
|
|
||||||
posts: list['BeatmapsetDiscussionPost']
|
|
||||||
timestamp: int | None = None
|
|
||||||
updated_at: datetime | None = None
|
|
||||||
user_id: int
|
|
||||||
|
|
||||||
|
class RankHistory(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
class BeatmapsetDiscussionPost(BaseModel):
|
mode: str
|
||||||
beatmapset_discussion_id: int
|
data: list[int]
|
||||||
created_at: datetime
|
|
||||||
deleted_at: datetime | None = None
|
|
||||||
deleted_by_id: int | None = None
|
|
||||||
id: int
|
|
||||||
last_editor_id: int | None = None
|
|
||||||
message: str
|
|
||||||
system: bool
|
|
||||||
updated_at: datetime | None = None
|
|
||||||
user_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapsetDiscussionVote(BaseModel):
|
class ActiveTournamentBanner(BaseModel):
|
||||||
beatmapset_discussion_id: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
created_at: datetime
|
|
||||||
id: int
|
|
||||||
score: int
|
|
||||||
updated_at: datetime
|
|
||||||
user_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapsetAvailability(BaseModel):
|
|
||||||
download_disabled: bool
|
|
||||||
more_information: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapsetHype(BaseModel):
|
|
||||||
current: int
|
|
||||||
required: int
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapsetNominationsSummary(BaseModel):
|
|
||||||
current: int
|
|
||||||
required: int
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapsetExtended(Beatmapset):
|
|
||||||
availability: BeatmapsetAvailability
|
|
||||||
bpm: float
|
|
||||||
can_be_hyped: float
|
|
||||||
deleted_at: datetime | None = None
|
|
||||||
discussion_enabled: bool
|
|
||||||
discussion_locked: bool
|
|
||||||
hype: BeatmapsetHype
|
|
||||||
is_scoreable: bool
|
|
||||||
last_updated: datetime
|
|
||||||
legacy_thread_url: str | None = None
|
|
||||||
nominations_summary: BeatmapsetNominationsSummary
|
|
||||||
ranked: int
|
|
||||||
ranked_date: datetime | None = None
|
|
||||||
rating: float
|
|
||||||
source: str
|
|
||||||
storyboard: bool
|
|
||||||
submitted_date: datetime | None = None
|
|
||||||
tags: str
|
|
||||||
|
|
||||||
|
|
||||||
class Build(BaseModel):
|
|
||||||
created_at: datetime
|
|
||||||
display_version: str
|
|
||||||
id: int
|
|
||||||
update_stream: 'UpdateStream | None' = None
|
|
||||||
users: int
|
|
||||||
version: str | None = None
|
|
||||||
youtube_id: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class Versions(BaseModel):
|
|
||||||
next: Build | None = None
|
|
||||||
previous: Build | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class ChangelogEntry(BaseModel):
|
|
||||||
category: str
|
|
||||||
created_at: datetime
|
|
||||||
github_pull_request_id: int | None = None
|
|
||||||
github_url: str | None = None
|
|
||||||
id: int | None = None
|
|
||||||
major: bool
|
|
||||||
repository_url: str | None = None
|
|
||||||
title: str | None = None
|
|
||||||
entry_type: str = Field(alias='type')
|
|
||||||
url: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class ChatChannel(BaseModel):
|
|
||||||
channel_id: int
|
|
||||||
name: str
|
|
||||||
description: str | None = None
|
|
||||||
icon: str | None = None
|
|
||||||
channel_type: Literal[
|
|
||||||
'PUBLIC',
|
|
||||||
'PRIVATE',
|
|
||||||
'MULTIPLAYER',
|
|
||||||
'SPECTATOR',
|
|
||||||
'TEMPORARY',
|
|
||||||
'PM',
|
|
||||||
'GROUP',
|
|
||||||
'ANNOUNCE',
|
|
||||||
] = Field(alias='type')
|
|
||||||
message_length_limit: int
|
|
||||||
moderated: bool
|
|
||||||
uuid: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
|
||||||
channel_id: int
|
|
||||||
content: str
|
|
||||||
is_action: bool
|
|
||||||
message_id: int
|
|
||||||
sender_id: int
|
|
||||||
timestamp: datetime
|
|
||||||
message_type: str = Field(alias='type')
|
|
||||||
uuid: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class Comment(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CommentBundle(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CommentSort(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CommentableMeta(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CurrentUserAttributes(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Event(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Forum(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ForumPost(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ForumTopic(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class GithubUser(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Group(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class KudosuHistory(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Match(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MatchEvent(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MatchGame(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MultiplayerScores(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MultiplayerScoresAroundUser(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MultiplayerScoresCursor(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class MultiplayerScoresSort(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NewsPost(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Nomination(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Notification(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class RankingType(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Rankings(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Score(BaseModel):
|
|
||||||
accuracy: float
|
|
||||||
beatmap_id: int | None = None
|
|
||||||
best_id: int | None = None
|
|
||||||
build_id: int | None = None
|
|
||||||
classic_total_score: int | None = None
|
|
||||||
ended_at: datetime | None = None
|
|
||||||
has_replay: bool | None = None
|
|
||||||
id: int
|
|
||||||
is_perfect_combo: bool | None = None
|
|
||||||
legacy_perfect: bool | None = None
|
|
||||||
legacy_score_id: int | None = None
|
|
||||||
legacy_total_score: int | None = None
|
|
||||||
max_combo: int
|
|
||||||
maximum_statistics: 'ScoreStatistics | None' = None
|
|
||||||
mods: list[str]
|
|
||||||
passed: bool
|
|
||||||
playlist_item_id: int | None = None
|
|
||||||
pp: float | None = None
|
|
||||||
preserve: bool | None = None
|
|
||||||
processed: bool | None = None
|
|
||||||
rank: str
|
|
||||||
ranked: bool | None = None
|
|
||||||
room_id: int | None = None
|
|
||||||
ruleset_id: int | None = None
|
|
||||||
started_at: datetime | None = None
|
|
||||||
statistics: 'ScoreStatistics'
|
|
||||||
total_score: int | None = None
|
|
||||||
type: str
|
|
||||||
user_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class ScoreStatistics(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Spotlight(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Spotlights(BaseModel):
|
|
||||||
spotlights: list[Spotlight]
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateStream(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserAccountHistory(BaseModel):
|
|
||||||
description: str | None = None
|
|
||||||
id: int
|
|
||||||
length: int
|
|
||||||
permanent: bool
|
|
||||||
timestamp: datetime
|
|
||||||
type: Literal['note', 'restriction', 'silence']
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileBanner(BaseModel):
|
|
||||||
id: int
|
id: int
|
||||||
tournament_id: int
|
tournament_id: int
|
||||||
image: str | None = None
|
image: str
|
||||||
image_2x: str | None = Field(alias='image@2x')
|
image_2x: str = Field(alias='image@2x')
|
||||||
|
|
||||||
|
|
||||||
class UserBadge(BaseModel):
|
class Badge(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
awarded_at: datetime
|
awarded_at: datetime
|
||||||
description: str
|
description: str
|
||||||
image_2x_url: str = Field(alias='image@2x_url')
|
image_2x_url: str = Field(alias='image@2x_url')
|
||||||
@ -458,146 +172,200 @@ class UserBadge(BaseModel):
|
|||||||
url: str
|
url: str
|
||||||
|
|
||||||
|
|
||||||
class UserKudosu(BaseModel):
|
class Team(BaseModel):
|
||||||
available: int
|
model_config = ConfigDict(extra='forbid')
|
||||||
total: int
|
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
short_name: str
|
||||||
|
flag_url: str
|
||||||
|
|
||||||
|
|
||||||
class UserRankHighest(BaseModel):
|
class ScoreStatistics(BaseModel):
|
||||||
rank: int
|
count_100: int
|
||||||
updated_at: datetime
|
count_300: int
|
||||||
|
count_50: int
|
||||||
|
count_geki: None
|
||||||
|
count_katu: None
|
||||||
|
count_miss: int
|
||||||
|
|
||||||
|
|
||||||
class UserMonthlyPlaycount(BaseModel):
|
class CurrentUserAttributes(BaseModel):
|
||||||
start_date: datetime
|
pin: None
|
||||||
count: int
|
|
||||||
|
|
||||||
|
class Covers(BaseModel):
|
||||||
|
cover: str
|
||||||
|
cover_2x: str = Field(..., alias='cover@2x')
|
||||||
|
card: str
|
||||||
|
card_2x: str = Field(..., alias='card@2x')
|
||||||
|
list: str
|
||||||
|
list_2x: str = Field(..., alias='list@2x')
|
||||||
|
slimcover: str
|
||||||
|
slimcover_2x: str = Field(..., alias='slimcover@2x')
|
||||||
|
|
||||||
|
|
||||||
|
class Beatmap(BaseModel):
|
||||||
|
beatmapset_id: int
|
||||||
|
difficulty_rating: float
|
||||||
|
id: int
|
||||||
|
mode: str
|
||||||
|
status: str
|
||||||
|
total_length: int
|
||||||
|
user_id: int
|
||||||
|
version: str
|
||||||
|
accuracy: float
|
||||||
|
ar: float
|
||||||
|
bpm: float
|
||||||
|
convert: bool
|
||||||
|
count_circles: int
|
||||||
|
count_sliders: int
|
||||||
|
count_spinners: int
|
||||||
|
cs: float
|
||||||
|
deleted_at: datetime | None
|
||||||
|
drain: float
|
||||||
|
hit_length: int
|
||||||
|
is_scoreable: bool
|
||||||
|
last_updated: str
|
||||||
|
mode_int: int
|
||||||
|
passcount: int
|
||||||
|
playcount: int
|
||||||
|
ranked: int
|
||||||
|
url: str
|
||||||
|
checksum: str
|
||||||
|
|
||||||
|
|
||||||
|
class Beatmapset(BaseModel):
|
||||||
|
anime_cover: bool
|
||||||
|
artist: str
|
||||||
|
artist_unicode: str
|
||||||
|
covers: Covers
|
||||||
|
creator: str
|
||||||
|
favourite_count: int
|
||||||
|
genre_id: int
|
||||||
|
hype: None
|
||||||
|
id: int
|
||||||
|
language_id: int
|
||||||
|
nsfw: bool
|
||||||
|
offset: int
|
||||||
|
play_count: int
|
||||||
|
preview_url: str
|
||||||
|
source: str
|
||||||
|
spotlight: bool
|
||||||
|
status: str
|
||||||
|
title: str
|
||||||
|
title_unicode: str
|
||||||
|
track_id: int | None
|
||||||
|
user_id: int
|
||||||
|
video: bool
|
||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
avatar_url: str | None = None
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
avatar_url: str
|
||||||
country_code: str
|
country_code: str
|
||||||
default_group: str | None = None
|
default_group: str
|
||||||
id: int
|
id: int
|
||||||
is_active: bool
|
is_active: bool
|
||||||
is_bot: bool
|
is_bot: bool
|
||||||
is_deleted: bool
|
is_deleted: bool
|
||||||
is_online: bool
|
is_online: bool
|
||||||
is_supporter: bool
|
is_supporter: bool
|
||||||
last_visit: datetime | None = None
|
last_visit: datetime | None
|
||||||
pm_friends_only: bool
|
pm_friends_only: bool
|
||||||
profile_colour: str | None = None
|
profile_colour: None
|
||||||
username: str
|
username: str
|
||||||
|
|
||||||
|
|
||||||
class UserExtended(User):
|
class Weight(BaseModel):
|
||||||
discord: str | None = None
|
percentage: float
|
||||||
|
pp: float
|
||||||
|
|
||||||
|
|
||||||
|
class Score(BaseModel):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
accuracy: float
|
||||||
|
best_id: int
|
||||||
|
created_at: str
|
||||||
|
id: int
|
||||||
|
max_combo: int
|
||||||
|
mode: str
|
||||||
|
mode_int: int
|
||||||
|
mods: list[str]
|
||||||
|
passed: bool
|
||||||
|
perfect: bool
|
||||||
|
pp: float | None
|
||||||
|
rank: str
|
||||||
|
replay: bool
|
||||||
|
score: int
|
||||||
|
statistics: ScoreStatistics
|
||||||
|
type: str
|
||||||
|
user_id: int
|
||||||
|
current_user_attributes: CurrentUserAttributes
|
||||||
|
beatmap: Beatmap
|
||||||
|
beatmapset: Beatmapset
|
||||||
|
user: User
|
||||||
|
weight: Weight | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class GetUser(User):
|
||||||
|
model_config = ConfigDict(extra='forbid')
|
||||||
|
|
||||||
|
cover_url: str
|
||||||
|
discord: str | None
|
||||||
has_supported: bool
|
has_supported: bool
|
||||||
interests: str | None = None
|
interests: None
|
||||||
join_date: datetime
|
join_date: str
|
||||||
location: str | None = None
|
location: str | None
|
||||||
max_blocks: int
|
max_blocks: int
|
||||||
max_friends: int
|
max_friends: int
|
||||||
occupation: str | None = None
|
occupation: None
|
||||||
playmode: Literal['osu', 'taiko', 'fruits', 'mania']
|
playmode: str
|
||||||
playstyle: list[str]
|
playstyle: list[str] | None
|
||||||
post_count: int
|
post_count: int
|
||||||
profile_hue: int | None = None
|
profile_hue: int | None
|
||||||
profile_order: list[str]
|
profile_order: list[str]
|
||||||
title: str | None = None
|
title: str | None
|
||||||
title_url: str | None = None
|
title_url: None
|
||||||
twitter: str | None = None
|
twitter: str | None
|
||||||
website: str | None = None
|
website: str | None
|
||||||
|
|
||||||
|
|
||||||
class UserGroup(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserSilence(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserStatisticsGradeCounts(BaseModel):
|
|
||||||
a: int
|
|
||||||
s: int
|
|
||||||
sh: int
|
|
||||||
ss: int
|
|
||||||
ssh: int
|
|
||||||
|
|
||||||
|
|
||||||
class UserStatisticsLevel(BaseModel):
|
|
||||||
current: int
|
|
||||||
progress: float
|
|
||||||
|
|
||||||
|
|
||||||
class UserStatistics(BaseModel):
|
|
||||||
count_100: int
|
|
||||||
count_300: int
|
|
||||||
count_50: int
|
|
||||||
count_miss: int
|
|
||||||
country_rank: int | None = None
|
|
||||||
grade_counts: UserStatisticsGradeCounts
|
|
||||||
hit_accuracy: float
|
|
||||||
is_ranked: bool
|
|
||||||
level: UserStatisticsLevel
|
|
||||||
maximum_combo: int
|
|
||||||
play_count: int
|
|
||||||
play_time: int
|
|
||||||
pp: float
|
|
||||||
global_rank: int | None = None
|
|
||||||
global_rank_exp: int | None = None
|
|
||||||
ranked_score: int
|
|
||||||
replays_watched_by_others: int
|
|
||||||
total_hits: int
|
|
||||||
total_score: int
|
|
||||||
|
|
||||||
|
|
||||||
class WikiPage(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FavoriteBeatmaps(BaseModel):
|
|
||||||
beatmapset_ids: list[int]
|
|
||||||
|
|
||||||
|
|
||||||
class BeatmapPacks(BaseModel):
|
|
||||||
beatmap_packs: list[BeatmapPack]
|
|
||||||
|
|
||||||
|
|
||||||
class UserBeatmapScores(BaseModel):
|
|
||||||
scores: list['Score']
|
|
||||||
|
|
||||||
|
|
||||||
class GetUserSchema(UserExtended):
|
|
||||||
account_history: list[UserAccountHistory]
|
|
||||||
active_tournament_banners: list[ProfileBanner]
|
|
||||||
badges: list[UserBadge]
|
|
||||||
beatmap_playcounts_count: int
|
|
||||||
country: Country
|
country: Country
|
||||||
# cover
|
cover: Cover
|
||||||
|
kudosu: Kudosu
|
||||||
|
account_history: list[str]
|
||||||
|
active_tournament_banner: ActiveTournamentBanner | None
|
||||||
|
active_tournament_banners: list[ActiveTournamentBanner]
|
||||||
|
badges: list[Badge]
|
||||||
|
beatmap_playcounts_count: int
|
||||||
|
comments_count: int
|
||||||
|
current_season_stats: None
|
||||||
|
daily_challenge_user_stats: DailyChallengeUserStats
|
||||||
favourite_beatmapset_count: int
|
favourite_beatmapset_count: int
|
||||||
# follow_user_mapping: list[int]
|
|
||||||
follower_count: int
|
follower_count: int
|
||||||
graveyard_beatmapset_count: int
|
graveyard_beatmapset_count: int
|
||||||
groups: list[UserGroup]
|
groups: list[str]
|
||||||
guest_beatmapset_count: int
|
guest_beatmapset_count: int
|
||||||
is_restricted: bool | None = None
|
|
||||||
kudosu: UserKudosu
|
|
||||||
loved_beatmapset_count: int
|
loved_beatmapset_count: int
|
||||||
mapping_follower_count: int
|
mapping_follower_count: int
|
||||||
monthly_playcounts: list[UserMonthlyPlaycount]
|
monthly_playcounts: list[MonthlyPlaycount]
|
||||||
nominated_beatmapset_count: int
|
nominated_beatmapset_count: int
|
||||||
# page
|
page: Page
|
||||||
pending_beatmapset_count: int
|
pending_beatmapset_count: int
|
||||||
previous_usernames: list[str]
|
previous_usernames: list[str]
|
||||||
rank_highest: UserRankHighest | None = None
|
rank_highest: RankHighest
|
||||||
# rank_history
|
|
||||||
ranked_beatmapset_count: int
|
ranked_beatmapset_count: int
|
||||||
# replays_watched_counts
|
replays_watched_counts: list[ReplaysWatchedCount]
|
||||||
scores_best_count: int
|
scores_best_count: int
|
||||||
scores_first_count: int
|
scores_first_count: int
|
||||||
|
scores_pinned_count: int
|
||||||
scores_recent_count: int
|
scores_recent_count: int
|
||||||
# session_verified: bool
|
|
||||||
statistics: UserStatistics
|
statistics: UserStatistics
|
||||||
support_level: int
|
support_level: int
|
||||||
# user_achievements
|
team: Team | None
|
||||||
|
user_achievements: list[UserAchievement]
|
||||||
|
rank_history: RankHistory
|
||||||
|
rankHistory: RankHistory
|
||||||
|
ranked_and_approved_beatmapset_count: int
|
||||||
|
unranked_beatmapset_count: int
|
||||||
|
|||||||
Reference in New Issue
Block a user