From d37cb44c91797a03dc83e05a35f29c99ce71a957 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 18:13:04 +0000 Subject: [PATCH 1/2] feat(chat): add !re & !pr command --- app/router/chat/banchobot.py | 59 ++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/app/router/chat/banchobot.py b/app/router/chat/banchobot.py index 65f1c8b..104e958 100644 --- a/app/router/chat/banchobot.py +++ b/app/router/chat/banchobot.py @@ -16,7 +16,7 @@ from app.database.score import Score from app.database.statistics import UserStatistics, get_rank from app.dependencies.fetcher import get_fetcher from app.exception import InvokeException -from app.models.mods import APIMod +from app.models.mods import APIMod, mod_to_save from app.models.multiplayer_hub import ( ChangeTeamRequest, ServerMultiplayerRoom, @@ -30,7 +30,8 @@ from app.signalr.hub.hub import Client from .server import server from httpx import HTTPError -from sqlmodel import func, select +from sqlalchemy.orm import joinedload +from sqlmodel import col, func, select from sqlmodel.ext.asyncio.session import AsyncSession HandlerResult = str | None | Awaitable[str | None] @@ -571,3 +572,57 @@ async def _mp(user: User, args: list[str], session: AsyncSession, channel: ChatC return f"No such command: {command}" return await _MP_COMMANDS[command](signalr_client, room, args[1:], session) + + +async def _score( + user_id: int, + session: AsyncSession, + include_fail: bool = False, + gamemode: GameMode | None = None, +) -> str: + q = ( + select(Score) + .where(Score.user_id == user_id) + .order_by(col(Score.id).desc()) + .options(joinedload(Score.beatmap)) + ) + if not include_fail: + q = q.where(Score.passed.is_(True)) + if gamemode is not None: + q = q.where(Score.gamemode == gamemode) + + score = (await session.exec(q)).first() + if score is None: + return "You have no scores." + + result = f"""{score.beatmap.beatmapset.title} [{score.beatmap.version}] ({score.gamemode.name.lower()}) +Played at {score.started_at} +{score.pp:.2f}pp {score.accuracy:.2%} {",".join(mod_to_save(score.mods))} {score.rank.name.upper()} +Great: {score.n300}, Good: {score.n100}, Meh: {score.n50}, Miss: {score.nmiss}""" # noqa: E501 + if score.gamemode == GameMode.MANIA: + keys = next( + (mod["acronym"] for mod in score.mods if mod["acronym"].endswith("K")), None + ) + if keys is None: + keys = f"{int(score.beatmap.cs)}K" + p_d_g = f"{score.ngeki / score.n300:.2f}:1" if score.n300 > 0 else "inf:1" + result += ( + f"\nKeys: {keys}, Perfect: {score.ngeki}, Ok: {score.nkatu}, P/G: {p_d_g}" + ) + return result + + +@bot.command("re") +async def _re(user: User, args: list[str], session: AsyncSession, channel: ChatChannel): + gamemode = None + if len(args) >= 1: + gamemode = GameMode.parse(args[0]) + return await _score(user.id, session, include_fail=True, gamemode=gamemode) + + +@bot.command("pr") +async def _pr(user: User, args: list[str], session: AsyncSession, channel: ChatChannel): + gamemode = None + if len(args) >= 1: + gamemode = GameMode.parse(args[0]) + return await _score(user.id, session, include_fail=False, gamemode=gamemode) From 1bc4687a0e266b472b315d253e81d5fba0239473 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Sun, 17 Aug 2025 18:14:34 +0000 Subject: [PATCH 2/2] fix(beatmap): always retry when status_code >= 400 --- app/fetcher/__init__.py | 2 +- app/fetcher/{osu_dot_direct.py => beatmap_raw.py} | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) rename app/fetcher/{osu_dot_direct.py => beatmap_raw.py} (86%) diff --git a/app/fetcher/__init__.py b/app/fetcher/__init__.py index 6d8e46d..a2390f8 100644 --- a/app/fetcher/__init__.py +++ b/app/fetcher/__init__.py @@ -1,8 +1,8 @@ from __future__ import annotations from .beatmap import BeatmapFetcher +from .beatmap_raw import BeatmapRawFetcher from .beatmapset import BeatmapsetFetcher -from .osu_dot_direct import BeatmapRawFetcher class Fetcher(BeatmapFetcher, BeatmapsetFetcher, BeatmapRawFetcher): diff --git a/app/fetcher/osu_dot_direct.py b/app/fetcher/beatmap_raw.py similarity index 86% rename from app/fetcher/osu_dot_direct.py rename to app/fetcher/beatmap_raw.py index 529eae4..f650df3 100644 --- a/app/fetcher/osu_dot_direct.py +++ b/app/fetcher/beatmap_raw.py @@ -2,7 +2,7 @@ from __future__ import annotations from ._base import BaseFetcher -from httpx import AsyncClient +from httpx import AsyncClient, HTTPError from httpx._models import Response from loguru import logger import redis.asyncio as redis @@ -22,12 +22,10 @@ class BeatmapRawFetcher(BaseFetcher): f"[BeatmapRawFetcher] get_beatmap_raw: {req_url}" ) resp = await self._request(req_url) - if resp.status_code == 429: + if resp.status_code >= 400: continue - elif resp.status_code < 400: - return resp.text - else: - resp.raise_for_status() + return resp.text + raise HTTPError("Failed to fetch beatmap") async def _request(self, url: str) -> Response: async with AsyncClient() as client: