Skip to content

Commit

Permalink
Better support for searches
Browse files Browse the repository at this point in the history
  • Loading branch information
cmyui committed Jun 22, 2024
1 parent 900b9c4 commit e93d4a7
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 31 deletions.
87 changes: 67 additions & 20 deletions app/adapters/osu_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,17 +155,23 @@ class Availability(BaseModel):
more_information: str | None


class GenreId(IntEnum): ... # TODO


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


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


class LanguageId(IntEnum): ... # TODO


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


Expand Down Expand Up @@ -264,7 +270,15 @@ class BeatmapsetSearchResponse(BaseModel):
total: int


class SearchLegacyRankedStatus(IntEnum):
class GeneralSetting(StrEnum):
RECOMMENDED = "recommended"
CONVERTS = "converts"
FOLLOWS = "follows"
SPOTLIGHTS = "spotlights"
FEATURED_ARTISTS = "featured_artists"


class Category(IntEnum):
RANKED = 0
FAVOURITES = 2
QUALIFIED = 3
Expand All @@ -278,41 +292,74 @@ class SearchLegacyRankedStatus(IntEnum):
def from_osu_api_status(
cls,
osu_api_status: RankedStatus,
) -> "SearchLegacyRankedStatus | None":
) -> "Category | None":
return {
RankedStatus.NOT_SUBMITTED: None,
RankedStatus.PENDING: SearchLegacyRankedStatus.PENDING,
RankedStatus.PENDING: Category.PENDING,
RankedStatus.UPDATE_AVAILABLE: None,
RankedStatus.RANKED: SearchLegacyRankedStatus.RANKED,
RankedStatus.APPROVED: SearchLegacyRankedStatus.RANKED,
RankedStatus.QUALIFIED: SearchLegacyRankedStatus.QUALIFIED,
RankedStatus.LOVED: SearchLegacyRankedStatus.LOVED,
RankedStatus.RANKED: Category.RANKED,
RankedStatus.APPROVED: Category.RANKED,
RankedStatus.QUALIFIED: Category.QUALIFIED,
RankedStatus.LOVED: Category.LOVED,
}.get(osu_api_status)


class SortBy(StrEnum):
ARTIST_ASC = "title_asc"
ARTIST_DESC = "title_desc"
DIFFICULTY_ASC = "artist_asc"
DIFFICULTY_DESC = "artist_desc"
FAVOURITES_ASC = "difficulty_asc"
FAVOURITES_DESC = "difficulty_desc"
PLAYS_ASC = "ranked_asc"
PLAYS_DESC = "ranked_desc"
RANKED_ASC = "rating_asc"
RANKED_DESC = "rating_desc"
RATING_ASC = "plays_asc"
RATING_DESC = "plays_desc"
TITLE_ASC = "favourites_asc"
TITLE_DES = "favourites_desc"


class Extra(StrEnum):
VIDEO = "video"
STORYBOARD = "storyboard"


async def search_beatmapsets(
query: str,
*,
ranked_status: SearchLegacyRankedStatus,
mode: GameMode,
page: int,
page_size: int,
general_settings: set[GeneralSetting] | None = None,
extras: set[Extra] | None = None,
mode: GameMode | None = None,
category: Category | None = None,
filter_nsfw: bool = True,
language_id: LanguageId | None = None,
genre_id: GenreId | None = None,
sort_by: SortBy | None = None,
page: int | None = None,
cursor_string: str | None = None,
) -> BeatmapsetSearchResponse:
# TODO: support more parameters. Here is a sample query:
# https://osu.ppy.sh/beatmapsets/search?e=&c=&g=&l=&m=&nsfw=&played=&q=&r=&sort=&s=&cursor_string=eyJhcHByb3ZlZF9kYXRlIjoxNzE4ODQ3Nzk0MDAwLCJpZCI6MjEzNDYyNX0
if [page, cursor_string].count(None) != 1:
raise ValueError("Exactly one of page or cursor_string must be provided")

osu_api_response_data: dict[str, Any] | None = None
try:
response = await osu_api_v2_http_client.get(
"beatmapsets/search",
params={
"q": query,
"e": ".".join(extras) if extras else "",
"c": ".".join(general_settings) if general_settings else "",
"g": genre_id.value if genre_id else "",
"l": language_id.value if language_id else "",
"m": mode,
"r": ranked_status,
"page": page,
"limit": page_size,
"cursor_string": cursor_string,
"nsfw": "" if filter_nsfw else "false",
"played": "",
"q": query,
"sort": sort_by.value if sort_by else "",
"s": category,
**({"page": page} if page else {}),
**({"cursor_string": cursor_string} if cursor_string else {}),
},
)
response.raise_for_status()
Expand Down
31 changes: 20 additions & 11 deletions app/api/v1/cheesegull.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
"""\
Aims to provide an API aligned with the Cheesegull API Specification.
API Spec: https://docs.ripple.moe/docs/cheesegull/cheesegull-api
This module is a FastAPI router that provides the following endpoints:
- GET /api/v1/cheesegull/b/{beatmap_id}
- GET /api/v1/cheesegull/s/{beatmapset_id}
- GET /api/v1/cheesegull/search
"""

import logging
from datetime import datetime
from enum import IntEnum
Expand All @@ -8,8 +19,7 @@
from pydantic import BaseModel

from app.adapters import osu_api_v2
from app.adapters.osu_api_v2 import SearchLegacyRankedStatus
from app.common_models import OsuDirectRankedStatus
from app.adapters.osu_api_v2 import Category
from app.common_models import RankedStatus

router = APIRouter()
Expand Down Expand Up @@ -110,7 +120,6 @@ def from_osu_api_beatmapset(
)


@router.get("/api/v1/cheesegull/b/{beatmap_id}")
@router.get("/api/b/{beatmap_id}")
async def cheesegull_beatmap(beatmap_id: int):
osu_api_beatmap = await osu_api_v2.get_beatmap(beatmap_id)
Expand All @@ -125,7 +134,6 @@ async def cheesegull_beatmap(beatmap_id: int):
return cheesegull_beatmap.model_dump()


@router.get("/api/v1/cheesegull/s/{beatmapset_id}")
@router.get("/api/s/{beatmapset_id}")
async def cheesegull_beatmapset(beatmapset_id: int):
osu_api_beatmapset = await osu_api_v2.get_beatmapset(beatmapset_id)
Expand All @@ -152,31 +160,32 @@ class CheesegullRankedStatus(IntEnum):

def get_osu_api_v2_search_ranked_status(
cheesegull_status: CheesegullRankedStatus,
) -> SearchLegacyRankedStatus | None:
) -> Category | None:
ranked_status = RankedStatus(cheesegull_status)
search_ranked_status = SearchLegacyRankedStatus.from_osu_api_status(ranked_status)
search_ranked_status = Category.from_osu_api_status(ranked_status)
return search_ranked_status


@router.get("/api/v1/cheesegull/search")
@router.get("/api/search")
async def cheesegull_search(
query: str,
status: CheesegullRankedStatus,
mode: osu_api_v2.GameMode,
page: int = 1,
page_size: int = Query(100, le=500), # TODO: verify osu! api max
offset: int = 1,
amount: int = Query(50, ge=1, le=100),
# TODO: auth, or at least per-ip ratelimit
):
ranked_status = get_osu_api_v2_search_ranked_status(status)
if ranked_status is None:
return Response(status_code=400)

page = offset // (amount + 1)

osu_api_search_response = await osu_api_v2.search_beatmapsets(
query=query,
ranked_status=ranked_status,
mode=mode,
category=ranked_status,
page=page,
page_size=page_size,
)
cheesegull_beatmapsets = [
CheesegullBeatmapset.from_osu_api_beatmapset(osu_api_beatmapset)
Expand Down

0 comments on commit e93d4a7

Please sign in to comment.