diff --git a/app/models/mods.py b/app/models/mods.py index b96811f..fc16238 100644 --- a/app/models/mods.py +++ b/app/models/mods.py @@ -79,15 +79,13 @@ class Mod(TypedDict): API_MODS: dict[Literal[0, 1, 2, 3], dict[str, Mod]] = {} - -def init_mods(): - mods_file = STATIC_DIR / "mods.json" - raw_mods = json.loads(mods_file.read_text()) - for ruleset in raw_mods: - ruleset_mods = {} - for mod in ruleset["Mods"]: - ruleset_mods[mod["Acronym"]] = mod - API_MODS[ruleset["RulesetID"]] = ruleset_mods +mods_file = STATIC_DIR / "mods.json" +raw_mods = json.loads(mods_file.read_text()) +for ruleset in raw_mods: + ruleset_mods = {} + for mod in ruleset["Mods"]: + ruleset_mods[mod["Acronym"]] = mod + API_MODS[ruleset["RulesetID"]] = ruleset_mods def int_to_mods(mods: int) -> list[APIMod]: diff --git a/app/models/multiplayer_hub.py b/app/models/multiplayer_hub.py index 5011af9..e583a69 100644 --- a/app/models/multiplayer_hub.py +++ b/app/models/multiplayer_hub.py @@ -22,7 +22,7 @@ from app.dependencies.database import with_db from app.dependencies.fetcher import get_fetcher from app.exception import InvokeException -from .mods import APIMod +from .mods import API_MODS, APIMod from .room import ( DownloadState, MatchType, @@ -121,19 +121,9 @@ class PlaylistItem(BaseModel): star_rating: float freestyle: bool - def _get_api_mods(self): - from app.models.mods import API_MODS, init_mods - - if not API_MODS: - init_mods() - return API_MODS - def _validate_mod_for_ruleset( self, mod: APIMod, ruleset_key: int, context: str = "mod" ) -> None: - from typing import Literal, cast - - API_MODS = self._get_api_mods() typed_ruleset_key = cast(Literal[0, 1, 2, 3], ruleset_key) # Check if mod is valid for ruleset diff --git a/app/models/score.py b/app/models/score.py index 26a67f1..b319703 100644 --- a/app/models/score.py +++ b/app/models/score.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Literal, TypedDict, cast from app.config import settings -from .mods import API_MODS, APIMod, init_mods +from .mods import API_MODS, APIMod from pydantic import BaseModel, Field, ValidationInfo, field_validator @@ -193,8 +193,6 @@ class SoloScoreSubmissionInfo(BaseModel): @field_validator("mods", mode="after") @classmethod def validate_mods(cls, mods: list[APIMod], info: ValidationInfo): - if not API_MODS: - init_mods() incompatible_mods = set() # check incompatible mods for mod in mods: diff --git a/tools/add_daily_challenge.py b/tools/add_daily_challenge.py new file mode 100644 index 0000000..6ae1761 --- /dev/null +++ b/tools/add_daily_challenge.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import datetime +import json +import os +import re +import sys + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from app.database import Beatmap +from app.dependencies.database import get_redis, with_db +from app.dependencies.fetcher import get_fetcher +from app.log import logger +from app.models.mods import APIMod, get_available_mods +from app.models.score import GameMode + +logger.remove() + + +def mod_inp(name: str, default: list[APIMod]) -> list[APIMod]: + mods_inp = input(f"Enter {name} mods (JSON APIMod Array) >>> ") + while True: + if mods_inp.strip() == "": + mods = default + break + try: + mods = json.loads(mods_inp) + except json.JSONDecodeError: + mods_inp = input(f"Invalid input. Enter {name} mods (JSON APIMod) >>> ") + continue + if not isinstance(mods, list): + mods_inp = input(f"Invalid input. Enter {name} mods (JSON APIMod) >>> ") + continue + break + return mods + + +async def main(): + async with with_db() as session: + redis = get_redis() + fetcher = await get_fetcher() + + today = datetime.date.today() + input_date = input(f"Enter a date ({today}) >>> ") + while True: + if not input_date: + input_date = str(today) + elif not re.match(r"^\d{4}-\d{2}-\d{2}$", input_date): + input_date = input(f"Invalid date format. Enter a date ({today}) >>> ") + continue + break + + beatmap_inp = input("Enter a beatmap ID >>> ") + while True: + if beatmap_inp.isdigit(): + beatmap_id = int(beatmap_inp) + break + beatmap_inp = input("Invalid input. Enter a beatmap ID >>> ") + beatmap = await Beatmap.get_or_fetch(session, fetcher, beatmap_id) + ruleset_inp = input(f"Enter ruleset ID ({int(beatmap.mode)}) >>> ") + while True: + if not ruleset_inp: + ruleset_inp = str(int(beatmap.mode)) + elif not ruleset_inp.isdigit(): + ruleset_inp = input( + f"Invalid input. Enter ruleset ID ({int(beatmap.mode)}) >>> " + ) + continue + ruleset_id = int(ruleset_inp) + if beatmap.mode != GameMode.OSU and ruleset_id != int(beatmap.mode): + ruleset_inp = input( + f"Invalid input. Enter ruleset ID ({int(beatmap.mode)}) >>> " + ) + continue + break + + required_mods = mod_inp("required", []) + allowed_mods = mod_inp("allowed", get_available_mods(ruleset_id, required_mods)) + + await redis.hset( # pyright: ignore[reportGeneralTypeIssues] + f"daily_challenge:{input_date}", + mapping={ + "beatmap": beatmap.id, + "ruleset_id": ruleset_id, + "required_mods": json.dumps(required_mods), + "allowed_mods": json.dumps(allowed_mods), + }, + ) + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main())