feat(beatmap): support lookup api

This commit is contained in:
MingxuanGame
2025-07-27 09:01:33 +00:00
parent a8b05f1574
commit 19895789ac
3 changed files with 47 additions and 7 deletions

View File

@@ -114,19 +114,25 @@ class Beatmap(BeatmapBase, table=True):
@classmethod @classmethod
async def get_or_fetch( async def get_or_fetch(
cls, session: AsyncSession, bid: int, fetcher: "Fetcher" cls,
session: AsyncSession,
fetcher: "Fetcher",
bid: int | None = None,
md5: str | None = None,
) -> "Beatmap": ) -> "Beatmap":
beatmap = ( beatmap = (
await session.exec( await session.exec(
select(Beatmap) select(Beatmap)
.where(Beatmap.id == bid) .where(
Beatmap.id == bid if bid is not None else Beatmap.checksum == md5
)
.options( .options(
joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType]
) )
) )
).first() ).first()
if not beatmap: if not beatmap:
resp = await fetcher.get_beatmap(bid) resp = await fetcher.get_beatmap(bid, md5)
r = await session.exec( r = await session.exec(
select(Beatmapset.id).where(Beatmapset.id == resp.beatmapset_id) select(Beatmapset.id).where(Beatmapset.id == resp.beatmapset_id)
) )

View File

@@ -8,11 +8,20 @@ from httpx import AsyncClient
class BeatmapFetcher(BaseFetcher): class BeatmapFetcher(BaseFetcher):
async def get_beatmap(self, beatmap_id: int) -> BeatmapResp: async def get_beatmap(
self, beatmap_id: int | None = None, beatmap_checksum: str | None = None
) -> BeatmapResp:
if beatmap_id:
params = {"id": beatmap_id}
elif beatmap_checksum:
params = {"checksum": beatmap_checksum}
else:
raise ValueError("Either beatmap_id or beatmap_checksum must be provided.")
async with AsyncClient() as client: async with AsyncClient() as client:
response = await client.get( response = await client.get(
f"https://osu.ppy.sh/api/v2/beatmaps/{beatmap_id}", "https://osu.ppy.sh/api/v2/beatmaps/lookup",
headers=self.header, headers=self.header,
params=params,
) )
response.raise_for_status() response.raise_for_status()
return BeatmapResp.model_validate(response.json()) return BeatmapResp.model_validate(response.json())

View File

@@ -34,6 +34,31 @@ from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
@router.get("/beatmaps/lookup", tags=["beatmap"], response_model=BeatmapResp)
async def lookup_beatmap(
id: int | None = Query(default=None, alias="id"),
md5: str | None = Query(default=None, alias="checksum"),
filename: str | None = Query(default=None, alias="filename"),
current_user: DBUser = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
fetcher: Fetcher = Depends(get_fetcher),
):
if id is None and md5 is None and filename is None:
raise HTTPException(
status_code=400,
detail="At least one of 'id', 'checksum', or 'filename' must be provided.",
)
try:
beatmap = await Beatmap.get_or_fetch(db, fetcher, bid=id, md5=md5)
except HTTPError:
raise HTTPException(status_code=404, detail="Beatmap not found")
if beatmap is None:
raise HTTPException(status_code=404, detail="Beatmap not found")
return BeatmapResp.from_db(beatmap)
@router.get("/beatmaps/{bid}", tags=["beatmap"], response_model=BeatmapResp) @router.get("/beatmaps/{bid}", tags=["beatmap"], response_model=BeatmapResp)
async def get_beatmap( async def get_beatmap(
bid: int, bid: int,
@@ -42,7 +67,7 @@ async def get_beatmap(
fetcher: Fetcher = Depends(get_fetcher), fetcher: Fetcher = Depends(get_fetcher),
): ):
try: try:
beatmap = await Beatmap.get_or_fetch(db, bid, fetcher) beatmap = await Beatmap.get_or_fetch(db, fetcher, bid)
return BeatmapResp.from_db(beatmap) return BeatmapResp.from_db(beatmap)
except HTTPError: except HTTPError:
raise HTTPException(status_code=404, detail="Beatmap not found") raise HTTPException(status_code=404, detail="Beatmap not found")
@@ -122,7 +147,7 @@ async def get_beatmap_attributes(
if ruleset_id is not None and ruleset is None: if ruleset_id is not None and ruleset is None:
ruleset = INT_TO_MODE[ruleset_id] ruleset = INT_TO_MODE[ruleset_id]
if ruleset is None: if ruleset is None:
beatmap_db = await Beatmap.get_or_fetch(db, beatmap, fetcher) beatmap_db = await Beatmap.get_or_fetch(db, fetcher, beatmap)
ruleset = beatmap_db.mode ruleset = beatmap_db.mode
key = ( key = (
f"beatmap:{beatmap}:{ruleset}:" f"beatmap:{beatmap}:{ruleset}:"