def from_parsed_match( cls, parsed_match: app.packets.MultiplayerMatch) -> "Match": obj = cls() obj.mods = Mods(parsed_match.mods) obj.name = parsed_match.name obj.passwd = parsed_match.passwd obj.map_name = parsed_match.map_name obj.map_id = parsed_match.map_id obj.map_md5 = parsed_match.map_md5 for slot, status, team, mods in zip( obj.slots, parsed_match.slot_statuses, parsed_match.slot_teams, parsed_match.slot_mods or [0] * 16, ): slot.status = SlotStatus(status) slot.team = MatchTeams(team) slot.mods = Mods(mods) # TODO: validate there is no hole here? obj.host_id = parsed_match.host_id obj.mode = GameMode(parsed_match.mode) obj.win_condition = MatchWinConditions(parsed_match.win_condition) obj.team_type = MatchTeamTypes(parsed_match.team_type) obj.freemods = parsed_match.freemods obj.seed = parsed_match.seed return obj
async def api_get_global_leaderboard( sort: Literal["tscore", "rscore", "pp", "acc"] = "pp", mode_arg: int = Query(0, alias="mode", ge=0, le=7), limit: int = Query(25, ge=1, le=100), offset: int = Query(0, min=0, max=2_147_483_647), country: Optional[str] = Query(None, min_length=2, max_length=2), db_conn: databases.core.Connection = Depends(acquire_db_conn), ): mode = GameMode(mode_arg) query_conditions = ["s.mode = :mode", "u.priv & 1", f"s.{sort} > 0"] query_parameters: dict[str, object] = {"mode": mode} if country is not None: query_conditions.append("u.country = :country") query_parameters["country"] = country rows = await db_conn.fetch_all( "SELECT u.id as player_id, u.name, u.country, s.tscore, s.rscore, " "s.pp, s.plays, s.playtime, s.acc, s.max_combo, " "s.xh_count, s.x_count, s.sh_count, s.s_count, s.a_count, " "c.id as clan_id, c.name as clan_name, c.tag as clan_tag " "FROM stats s " "LEFT JOIN users u USING (id) " "LEFT JOIN clans c ON u.clan_id = c.id " f"WHERE {' AND '.join(query_conditions)} " f"ORDER BY s.{sort} DESC LIMIT :offset, :limit", query_parameters | {"offset": offset, "limit": limit}, ) return ORJSONResponse( {"status": "success", "leaderboard": [dict(row) for row in rows]}, )
async def from_sql(cls, score_id: int, scores_table: str) -> Optional["Score"]: """Create a score object from sql using it's scoreid.""" # XXX: perhaps in the future this should take a gamemode rather # than just the sql table? just faster on the current setup :P row = await app.state.services.database.fetch_one( "SELECT id, map_md5, userid, pp, score, " "max_combo, mods, acc, n300, n100, n50, " "nmiss, ngeki, nkatu, grade, perfect, " "status, mode, play_time, " "time_elapsed, client_flags, online_checksum " f"FROM {scores_table} WHERE id = :score_id", {"score_id": score_id}, ) if not row: return s = cls() s.id = row[0] s.bmap = await Beatmap.from_md5(row[1]) s.player = await app.state.sessions.players.from_cache_or_sql(id=row[2]) s.sr = 0.0 # TODO ( s.pp, s.score, s.max_combo, s.mods, s.acc, s.n300, s.n100, s.n50, s.nmiss, s.ngeki, s.nkatu, s.grade, s.perfect, s.status, mode_vn, s.play_time, s.time_elapsed, s.client_flags, s.online_checksum, ) = row[3:] # fix some types s.passed = s.status != 0 s.status = SubmissionStatus(s.status) s.grade = Grade.from_str(s.grade) s.mods = Mods(s.mods) s.mode = GameMode.from_params(mode_vn, s.mods) s.client_flags = ClientFlags(s.client_flags) if s.bmap: s.rank = await s.calc_lb_placement() return s
def _parse_from_osuapi_resp(self, osuapi_resp: dict[str, Any]) -> None: """Change internal data with the data in osu!api format.""" # NOTE: `self` is not guaranteed to have any attributes # initialized when this is called. self.md5 = osuapi_resp["file_md5"] # self.id = int(osuapi_resp['beatmap_id']) self.set_id = int(osuapi_resp["beatmapset_id"]) self.artist, self.title, self.version, self.creator = ( osuapi_resp["artist"], osuapi_resp["title"], osuapi_resp["version"], osuapi_resp["creator"], ) self.filename = (( "{artist} - {title} ({creator}) [{version}].osu").format( **osuapi_resp).translate(IGNORED_BEATMAP_CHARS)) # quite a bit faster than using dt.strptime. _last_update = osuapi_resp["last_update"] self.last_update = datetime( year=int(_last_update[0:4]), month=int(_last_update[5:7]), day=int(_last_update[8:10]), hour=int(_last_update[11:13]), minute=int(_last_update[14:16]), second=int(_last_update[17:19]), ) self.total_length = int(osuapi_resp["total_length"]) if osuapi_resp["max_combo"] is not None: self.max_combo = int(osuapi_resp["max_combo"]) else: self.max_combo = 0 # if a map is 'frozen', we keeps it's status # even after an update from the osu!api. if not getattr(self, "frozen", False): osuapi_status = int(osuapi_resp["approved"]) self.status = RankedStatus.from_osuapi(osuapi_status) self.mode = GameMode(int(osuapi_resp["mode"])) if osuapi_resp["bpm"] is not None: self.bpm = float(osuapi_resp["bpm"]) else: self.bpm = 0.0 self.cs = float(osuapi_resp["diff_size"]) self.od = float(osuapi_resp["diff_overall"]) self.ar = float(osuapi_resp["diff_approach"]) self.hp = float(osuapi_resp["diff_drain"]) self.diff = float(osuapi_resp["difficultyrating"])
async def api_get_player_most_played( user_id: Optional[int] = Query(None, alias="id", ge=3, le=2_147_483_647), username: Optional[str] = Query(None, alias="name", regex=regexes.USERNAME.pattern), mode_arg: int = Query(0, alias="mode", ge=0, le=7), limit: int = Query(25, ge=1, le=100), db_conn: databases.core.Connection = Depends(acquire_db_conn), ): """Return the most played beatmaps of a given player.""" # NOTE: this will almost certainly not scale well, lol. if user_id is not None: p = await app.state.sessions.players.from_cache_or_sql(id=user_id) elif username is not None: p = await app.state.sessions.players.from_cache_or_sql(name=username) else: return ORJSONResponse( {"status": "Must provide either id or name."}, status_code=status.HTTP_400_BAD_REQUEST, ) if not p: return ORJSONResponse( {"status": "Player not found."}, status_code=status.HTTP_404_NOT_FOUND, ) # parse args (mode, limit) mode = GameMode(mode_arg) # fetch & return info from sql rows = await db_conn.fetch_all( "SELECT m.md5, m.id, m.set_id, m.status, " "m.artist, m.title, m.version, m.creator, COUNT(*) plays " f"FROM {mode.scores_table} s " "INNER JOIN maps m ON m.md5 = s.map_md5 " "WHERE s.userid = :user_id " "AND s.mode = :mode_vn " "GROUP BY s.map_md5 " "ORDER BY plays DESC " "LIMIT :limit", {"user_id": p.id, "mode_vn": mode.as_vanilla, "limit": limit}, ) return ORJSONResponse( { "status": "success", "maps": [dict(row) for row in rows], }, )
async def from_submission(cls, data: list[str]) -> "Score": """Create a score object from an osu! submission string.""" s = cls() """ parse the following format # 0 online_checksum # 1 n300 # 2 n100 # 3 n50 # 4 ngeki # 5 nkatu # 6 nmiss # 7 score # 8 max_combo # 9 perfect # 10 grade # 11 mods # 12 passed # 13 gamemode # 14 play_time # yyMMddHHmmss # 15 osu_version + (" " * client_flags) """ s.online_checksum = data[0] s.n300, s.n100, s.n50, s.ngeki, s.nkatu, s.nmiss, s.score, s.max_combo = map( int, data[1:9], ) s.perfect = data[9] == "True" _grade = data[10] # letter grade s.mods = Mods(int(data[11])) s.passed = data[12] == "True" s.mode = GameMode.from_params(int(data[13]), s.mods) # TODO: we might want to use data[14] to get more # accurate submission time (client side) but # we'd probably want to check if it's close. s.play_time = datetime.now() s.client_flags = ClientFlags(data[15].count(" ") & ~4) s.grade = Grade.from_str(_grade) if s.passed else Grade.F return s
def __init__(self, map_set: "BeatmapSet", **kwargs: Any) -> None: self.set = map_set self.md5 = kwargs.get("md5", "") self.id = kwargs.get("id", 0) self.set_id = kwargs.get("set_id", 0) self.artist = kwargs.get("artist", "") self.title = kwargs.get("title", "") self.version = kwargs.get("version", "") # diff name self.creator = kwargs.get("creator", "") self.last_update = kwargs.get("last_update", DEFAULT_LAST_UPDATE) self.total_length = kwargs.get("total_length", 0) self.max_combo = kwargs.get("max_combo", 0) self.status = RankedStatus(kwargs.get("status", 0)) self.frozen = kwargs.get("frozen", False) == 1 self.plays = kwargs.get("plays", 0) self.passes = kwargs.get("passes", 0) self.mode = GameMode(kwargs.get("mode", 0)) self.bpm = kwargs.get("bpm", 0.0) self.cs = kwargs.get("cs", 0.0) self.od = kwargs.get("od", 0.0) self.ar = kwargs.get("ar", 0.0) self.hp = kwargs.get("hp", 0.0) self.diff = kwargs.get("diff", 0.0) self.filename = kwargs.get("filename", "") self._pp_cache = { 0: {}, 1: {}, 2: {}, 3: {}, } # {mode_vn: {mods: (acc/score: pp, ...), ...}}
async def api_get_map_scores( scope: Literal["recent", "best"], map_id: Optional[int] = Query(None, alias="id", ge=0, le=2_147_483_647), map_md5: Optional[str] = Query(None, alias="md5", min_length=32, max_length=32), mods_arg: Optional[str] = Query(None, alias="mods"), mode_arg: int = Query(0, alias="mode", ge=0, le=7), limit: int = Query(50, ge=1, le=100), db_conn: databases.core.Connection = Depends(acquire_db_conn), ): """Return the top n scores on a given beatmap.""" if map_id is not None: bmap = await Beatmap.from_bid(map_id) elif map_md5 is not None: bmap = await Beatmap.from_md5(map_md5) else: return ORJSONResponse( {"status": "Must provide either id or md5!"}, status_code=status.HTTP_400_BAD_REQUEST, ) if not bmap: return ORJSONResponse( {"status": "Map not found."}, status_code=status.HTTP_404_NOT_FOUND, ) # parse args (scope, mode, mods, limit) mode = GameMode(mode_arg) if mods_arg is not None: if mods_arg[0] in ("~", "="): # weak/strong equality strong_equality = mods_arg[0] == "=" mods_arg = mods_arg[1:] else: # use strong as default strong_equality = True if mods_arg.isdecimal(): # parse from int form mods = Mods(int(mods_arg)) else: # parse from string form mods = Mods.from_modstr(mods_arg) else: mods = None # NOTE: userid will eventually become player_id, # along with everywhere else in the codebase. query = [ "SELECT s.map_md5, s.score, s.pp, s.acc, s.max_combo, s.mods, " "s.n300, s.n100, s.n50, s.nmiss, s.ngeki, s.nkatu, s.grade, s.status, " "s.mode, s.play_time, s.time_elapsed, s.userid, s.perfect, " "u.name player_name, " "c.id clan_id, c.name clan_name, c.tag clan_tag " f"FROM {mode.scores_table} s " "INNER JOIN users u ON u.id = s.userid " "LEFT JOIN clans c ON c.id = u.clan_id " "WHERE s.map_md5 = :map_md5 " "AND s.mode = :mode_vn " "AND s.status = 2 " "AND u.priv & 1", ] params: dict[str, object] = { "map_md5": bmap.md5, "mode_vn": mode.as_vanilla, } if mods is not None: if strong_equality: # type: ignore query.append("AND mods & :mods = :mods") else: query.append("AND mods & :mods != 0") params["mods"] = mods # unlike /get_player_scores, we'll sort by score/pp depending # on the mode played, since we want to replicated leaderboards. if scope == "best": sort = "pp" if mode >= GameMode.RELAX_OSU else "score" else: # recent sort = "play_time" query.append(f"ORDER BY {sort} DESC LIMIT :limit") params["limit"] = limit rows = await db_conn.fetch_all(" ".join(query), params) return ORJSONResponse( { "status": "success", "scores": [dict(row) for row in rows], }, )
async def api_get_player_scores( scope: Literal["recent", "best"], user_id: Optional[int] = Query(None, alias="id", ge=3, le=2_147_483_647), username: Optional[str] = Query(None, alias="name", regex=regexes.USERNAME.pattern), mods_arg: Optional[str] = Query(None, alias="mods"), mode_arg: int = Query(0, alias="mode", ge=0, le=7), limit: int = Query(25, ge=1, le=100), include_loved: bool = False, include_failed: bool = True, ): """Return a list of a given user's recent/best scores.""" if username and user_id: return ORJSONResponse( {"status": "Must provide either id OR name!"}, status_code=status.HTTP_400_BAD_REQUEST, ) if username: player = await app.state.sessions.players.from_cache_or_sql(name=username) elif user_id: player = await app.state.sessions.players.from_cache_or_sql(id=user_id) else: return ORJSONResponse( {"status": "Must provide either id OR name!"}, status_code=status.HTTP_400_BAD_REQUEST, ) if not player: return ORJSONResponse( {"status": "Player not found."}, status_code=status.HTTP_404_NOT_FOUND, ) # parse args (scope, mode, mods, limit) mode = GameMode(mode_arg) if mods_arg is not None: if mods_arg[0] in ("~", "="): # weak/strong equality strong_equality = mods_arg[0] == "=" mods_arg = mods_arg[1:] else: # use strong as default strong_equality = True if mods_arg.isdecimal(): # parse from int form mods = Mods(int(mods_arg)) else: # parse from string form mods = Mods.from_modstr(mods_arg) else: mods = None # build sql query & fetch info query = [ "SELECT t.id, t.map_md5, t.score, t.pp, t.acc, t.max_combo, " "t.mods, t.n300, t.n100, t.n50, t.nmiss, t.ngeki, t.nkatu, t.grade, " "t.status, t.mode, t.play_time, t.time_elapsed, t.perfect " f"FROM {mode.scores_table} t " "INNER JOIN maps b ON t.map_md5 = b.md5 " "WHERE t.userid = :user_id AND t.mode = :mode_vn", ] params: dict[str, object] = { "user_id": player.id, "mode_vn": mode.as_vanilla, } if mods is not None: if strong_equality: # type: ignore query.append("AND t.mods & :mods = :mods") else: query.append("AND t.mods & :mods != 0") params["mods"] = mods if scope == "best": allowed_statuses = [2, 3] if include_loved: allowed_statuses.append(5) query.append("AND t.status = 2 AND b.status IN :statuses") params["statuses"] = allowed_statuses sort = "t.pp" else: if not include_failed: query.append("AND t.status != 0") sort = "t.play_time" query.append(f"ORDER BY {sort} DESC LIMIT :limit") params["limit"] = limit rows = [ dict(row) for row in await app.state.services.database.fetch_all(" ".join(query), params) ] # fetch & return info from sql for row in rows: bmap = await Beatmap.from_md5(row.pop("map_md5")) row["beatmap"] = bmap.as_dict if bmap else None player_info = { "id": player.id, "name": player.name, "clan": { "id": player.clan.id, "name": player.clan.name, "tag": player.clan.tag, } if player.clan else None, } return ORJSONResponse( { "status": "success", "scores": rows, "player": player_info, }, )