async def update_stats(self, mode: osuModes, table: str, mode_vn: int) -> None: stats = self.stats[mode.value] mode_name = mode.name s = await glob.db.fetch( 'SELECT {0}.acc, {0}.pp FROM {0} ' 'LEFT OUTER JOIN maps ON maps.md5 = {0}.md5 ' 'WHERE {0}.uid = %s AND {0}.mode = %s ' 'AND {0}.status = 2 AND maps.status IN (2, 3) ' 'ORDER BY {0}.pp DESC'.format(table), [self.id, mode_vn] ) if not s: return # no scores xd t100 = s[:100] stats.acc = sum([row['acc'] for row in s[:50]]) / min(50, len(t100)) weighted = sum([row['pp'] * 0.95 ** i for i, row in enumerate(t100)]) bonus = 416.6667 * (1 - 0.9994 ** len(s)) stats.pp = round(weighted + bonus) if not self.restricted: await glob.redis.zadd(f'asahi:leaderboard:{mode_name}', stats.pp, self.id) await glob.redis.zadd(f'asahi:leaderboard:{mode_name}:{self.country_iso}', stats.pp, self.id) r = await glob.redis.zrevrank(f'asahi:leaderboard:{mode_name}', self.id) cr = await glob.redis.zrevrank(f'asahi:leaderboard:{mode_name}:{self.country_iso}', self.id) stats.rank = 0 stats.country_rank = 0 if r is None: if stats.pp > 0: stats.rank = 1 else: stats.rank = r + 1 if cr is None: if stats.pp > 0: stats.country_rank = 1 else: stats.country_rank = cr + 1 await glob.db.execute( 'UPDATE stats SET rscore_{0} = %s, acc_{0} = %s, pc_{0} = %s, tscore_{0} = %s,' ' pp_{0} = %s, mc_{0} = %s, pt_{0} = %s WHERE id = %s'.format(mode_name), [stats.rscore, stats.acc, stats.pc, stats.tscore, stats.pp, stats.max_combo, stats.playtime, self.id] ) self.enqueue(writer.userStats(self)) if not self.restricted: glob.players.enqueue(writer.userStats(self))
async def update_action(user: Player, p: bytes) -> None: d = reader.handle_packet(p, ( ('actionid', osuTypes.u8), ('info', osuTypes.string), ('md5', osuTypes.string), ('mods', osuTypes.u32), ('mode', osuTypes.u8), ('mid', osuTypes.i32) )) if d['actionid'] == 0 and d['mods'] & Mods.RELAX: d['info'] = 'on Relax' elif d['actionid'] == 0 and d['mods'] & Mods.AUTOPILOT: d['info'] = 'on Autopilot' user.action = d['actionid'] user.info = d['info'] user.map_md5 = d['md5'] user.mods = d['mods'] m = lbModes(d['mode'], d['mods']) user.mode = m.value user.mode_vn = m.as_vn user.map_id = d['mid'] if d['actionid'] == 2: user.info += f' +{(Mods(user.mods))!r}' # ugly and i dont care! if not user.restricted: glob.players.enqueue(writer.userStats(user))
async def update_action(user: Player, p: bytes) -> None: d = reader.handle_packet( p, ( ("actionid", osuTypes.u8), ("base_info", osuTypes.string), ("md5", osuTypes.string), ("mods", osuTypes.u32), ("mode", osuTypes.u8), ("mid", osuTypes.i32), ), ) if d["actionid"] == 0 and d["mods"] & Mods.RELAX: d["base_info"] = "on Relax" elif d["actionid"] == 0 and d["mods"] & Mods.AUTOPILOT: d["base_info"] = "on Autopilot" user.action = d["actionid"] user.base_info = d["base_info"] user.map_md5 = d["md5"] user.mods = d["mods"] m = lbModes(d["mode"], d["mods"]) user.mode = m.value user.mode_vn = m.as_vn user.map_id = d["mid"] if d["actionid"] == 2: user.base_info += f" +{(Mods(user.mods))!r}" # ugly and i dont care! if not user.restricted: glob.players.enqueue(writer.userStats(user))
async def scoreSubmit(request: Request) -> bytes: mpargs = request.args s = await Score.from_submission(mpargs['score'], mpargs['iv'], mpargs['pass'], mpargs['osuver']) if not s: return b'error: no' elif not s.user: return b'' # player not online, make client make resubmit attempts elif not s.map: return b'error: beatmap' # map unsubmitted elif s.mods & Mods.UNRANKED: return b'error: no' if s.mode != s.user.mode or s.mods != s.user.mods: s.user.mode = s.mode.value s.user.mods = s.mods glob.players.enqueue(writer.userStats(s.user)) # submit score and get id xd s.id = await glob.db.execute( f'INSERT INTO {s.mode.table} (md5, score, acc, pp, combo, mods, n300, ' f'geki, n100, katu, n50, miss, grade, status, mode, time, uid, readable_mods, fc) VALUES ' f'(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', [ s.map.md5, s.score, s.acc, s.pp, s.combo, int(s.mods), s.n300, s.geki, s.n100, s.katu, s.n50, s.miss, s.grade, s.status.value, s.mode.as_vn, s.time, s.user.id, s.readable_mods, s.fc ]) if s.status == scoreStatuses.Best: # set any other best scores to submitted ones as they've been overwritten await glob.db.execute( f'UPDATE {s.mode.table} SET status = 1 WHERE status = 2 AND uid = %s AND md5 = %s AND mode = %s AND id != %s', [s.user.id, s.map.md5, s.mode.as_vn, s.id]) # save replay if not a failed score if s.status != scoreStatuses.Failed: files = request.files replay = files.get('score') # i will make this auto-parse the replays one day when im not lazy if s.mods & Mods.RELAX: f = rx_path / f'{s.id}.osr' elif s.mods & Mods.AUTOPILOT: f = ap_path / f'{s.id}.osr' else: f = vn_path / f'{s.id}.osr' f.write_bytes(replay) if glob.config.anticheat and s.user.priv & Privileges.BypassAnticheat: loop = asyncio.get_event_loop() loop.create_task(s.analyse()) cap = glob.config.pp_caps[s.mode.value] if cap is not None and s.pp >= cap \ and s.map.status & mapStatuses.GIVE_PP and glob.config.anticheat \ and not s.user.restricted and not s.user.priv & Privileges.Whitelisted: # ugly await s.user.restrict( reason=f'Exceeding PP cap ({s.pp:,}pp) on {s.map.name}', fr=glob.bot) # update stats EEEEEEE stats = s.user.stats[s.mode.value] old = copy.copy( stats) # we need a copy of the old stats for submission chart elapsed = mpargs.get( 'st' if s.passed else 'ft') # timewarp check with this soon? if not elapsed and s.user.priv & Privileges.BypassAnticheat: await s.user.restrict( 'Modified client', fr=glob.bot ) # its really only old version, but its supposed to be blocked on login. if it isnt present it must be modified to seem like a new version stats.playtime += int(elapsed) // 1000 stats.tscore += s.score stats.pc += 1 if s.status == scoreStatuses.Best and s.map.status >= mapStatuses.Ranked: add = s.score if s.old_best: add -= s.old_best.score if s.combo > old.max_combo: stats.max_combo = s.combo if s.map.status & mapStatuses.GIVE_PP: stats.rscore += add await s.user.update_stats(s.mode, s.mode.table, s.mode.as_vn) if not s.user.restricted: s.map.plays += 1 if s.passed: s.map.passes += 1 await glob.db.execute( 'UPDATE maps SET plays = %s, passes = %s WHERE md5 = %s', [s.map.plays, s.map.passes, s.map.md5]) # sub charts bruh if s.mods & Mods.GAME_CHANGING or s.status == scoreStatuses.Failed: log( f'[{s.mode!r}] {s.user.name} submitted a score on {s.map.name} ({s.status.name})', Ansi.LBLUE) return b'error: no' # not actually erroring, score is already submitted we just want client to stop request as we cannot provide chart achievements = '' if s.map.status & mapStatuses.GIVE_PP and not s.user.restricted: # TODO: hush-hush etc. achievements achs = [] for ach in glob.achievements: if ach in s.user.achievements: continue if ach.cond(s): await s.user.unlock_ach(ach) achs.append(ach) achievements = '/'.join([a.format for a in achs]) charts = [] # could be done better def chart_format(name, b, a): return f'{name}Before:{b or ""}|{name}After:{a}' # osu makes this so ugly man. # map info charts.append(f'beatmapId:{s.map.id}|' f'beatmapSetId:{s.map.sid}|' f'beatmapPlaycount:{s.map.plays}|' f'beatmapPasscount:{s.map.passes}|' f'approvedDate:{s.map.update}') # score-specific ranking if s.map.status >= mapStatuses.Ranked: charts.append('|'.join(( 'chartId:beatmap', f'chartUrl:{s.map.set_url}', 'chartName:Current Score', *(( # wtaf chart_format('rank', s.old_best.rank, s.rank), chart_format('rankedScore', s.old_best.score, s.score), chart_format('totalScore', s.old_best.score, s.score), chart_format('maxCombo', s.old_best.combo, s.combo), chart_format('accuracy', round(s.old_best.acc, 2), round(s.acc, 2)), chart_format('pp', s.old_best.pp, s.pp)) if s.old_best else (chart_format('rank', None, s.rank), chart_format('rankedScore', None, s.score), chart_format('totalScore', None, s.score), chart_format('maxCombo', None, s.combo), chart_format('accuracy', None, round(s.acc, 2)), chart_format('pp', None, s.pp))), f'onlineScoreId:{s.id}'))) # overall user stats charts.append('|'.join( ('chartId:overall', f'chartUrl:https://{glob.config.domain}/u/{s.user.id}', 'chartName:Overall Stats', *((chart_format('rank', old.rank, stats.rank), chart_format('rankedScore', old.rscore, stats.rscore), chart_format('totalScore', old.tscore, stats.tscore), chart_format('maxCombo', old.max_combo, stats.max_combo), chart_format('accuracy', round(old.acc, 2), round(stats.acc, 2)), chart_format('pp', old.pp, stats.pp)) if old else (chart_format('rank', None, stats.rank), chart_format('rankedScore', None, stats.rscore), chart_format('totalScore', None, stats.tscore), chart_format('maxCombo', None, stats.max_combo), chart_format('accuracy', None, round(stats.acc, 2)), chart_format('pp', None, stats.pp))), f'achievements-new:{achievements}'))) if s.status == scoreStatuses.Best and s.map.status >= mapStatuses.Ranked: if s.rank == 1: # announce #1 to announce channel cus they achieved #1 loop = asyncio.get_event_loop() loop.create_task(s.announce_n1()) # update lb cache lb = getattr(s.map, s.mode.leaderboard) if lb: threading.Thread(target=lb.set_user_pb, args=( s.user, s, )).start() s.user.last_score = s log( f'[{s.mode!r}] {s.user.name} submitted a score on {s.map.name} ({s.status.name})', Ansi.LBLUE) return '\n'.join(charts).encode() # thank u osu
if (md5 := args['c']) in glob.cache['unsub']: return b'-1|false' # tell client map is unsub xd mods = int(args['mods']) mode = lbModes(int(args['m']), mods) lbm = int(args['v']) player = request.extras.get('player') if mode.value != player.mode or mods != player.mods: player.mode = mode.value player.mode_vn = mode.as_vn player.mods = mods if not player.restricted: glob.players.enqueue(writer.userStats(player)) bmap = await Beatmap.from_md5(md5) if not bmap: file = args['f'].replace('+', '') if not (info := regexes.map_file.match( unquote(file))): # once again osu why # invalid file? idfk glob.cache['unsub'].append(md5) return b'-1|false' exists = await glob.db.fetchval( 'SELECT 1 FROM maps WHERE artist = %s AND title = %s AND diff = %s AND mapper = %s', [info['artist'], info['title'], info['diff'], info['mapper']])
async def request_stats(user: Player, p: bytes) -> None: uids = (reader.handle_packet(p, (('uids', osuTypes.i32_list),)))['uids'] for o in glob.players.unrestricted_users: if o.id != user.id and o.id in uids: user.enqueue(writer.userStats(o))
async def update_stats(user: Player, _) -> None: user.enqueue(writer.userStats(user))
# we want to check multi stuff before any cheats just in case await checks.multi_check() # this is probably confusing syntax. # client_check will restrict if a client is custom and if that's the case we want to ignore any update checks. # if an update check is made, this will send update required packet if thats what the function returns. # i should probably rename these funcs in the future if not await checks.client_check() and await checks.version_check(): request.resp_headers['cho-token'] = 'no' return writer.versionUpdateForced() + writer.userID(-2) # start enqueueing login data to the client data = bytearray(writer.userID(p.id)) # initiate login by providing the user's id data += writer.protocolVersion(19) # no clue what this does data += writer.banchoPrivileges(p.client_priv | ClientPrivileges.Supporter) data += writer.userPresence(p) + writer.userStats(p) # provide user & other user's presence/stats (for f9 + user stats) data += writer.channelInfoEnd() # no clue what this does either data += writer.menuIcon() # set main menu icon data += writer.friends(p.friends) # send user friend list data += writer.silenceEnd(p.silence_end) # get channels from cache and send to user for chan in glob.channels.values(): if chan.auto: p.join_chan(chan) data += writer.channelJoin(chan.name) # only join user to channel if the channel is meant for purpose data += writer.channelInfo(chan) # regardless of whether the channel should be auto-joined we should make the client aware of it # add user to cache? glob.players.append(p)
async def scoreSubmit(request: Request) -> bytes: mpargs = request.args s = await Score.from_submission( mpargs["score"], mpargs["iv"], mpargs["pass"], mpargs["osuver"], ) if not s: return b"error: no" elif not s.user: return b"" # player not online, make client make resubmit attempts elif not s.map: return b"error: beatmap" # map unsubmitted elif s.mods & Mods.UNRANKED: return b"error: no" if s.mode != s.user.mode or s.mods != s.user.mods: s.user.mode = s.mode.value s.user.mods = s.mods glob.players.enqueue(writer.userStats(s.user)) # submit score and get id xd s.id = await glob.db.execute( f"INSERT INTO {s.mode.table} (md5, score, acc, pp, combo, mods, n300, " f"geki, n100, katu, n50, miss, grade, status, mode, time, uid, readable_mods, fc) VALUES " f"(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", [ s.map.md5, s.score, s.acc, s.pp, s.combo, int(s.mods), s.n300, s.geki, s.n100, s.katu, s.n50, s.miss, s.grade, s.status.value, s.mode.as_vn, s.time, s.user.id, s.readable_mods, s.fc, ], ) if s.status == scoreStatuses.Best: # set any other best scores to submitted ones as they've been overwritten await glob.db.execute( f"UPDATE {s.mode.table} SET status = 1 WHERE status = 2 AND uid = %s AND md5 = %s AND mode = %s AND id != %s", [s.user.id, s.map.md5, s.mode.as_vn, s.id], ) # save replay if not a failed score if s.status != scoreStatuses.Failed: files = request.files try: replay = files["score"] except KeyError: await s.player.restrict("Missing replay file", fr=glob.bot) return b"error: ban" if replay == b"\r\n": await s.player.restrict("Missing replay file", fr=glob.bot) return b"error: ban" # i will make this auto-parse the replays one day when im not lazy if s.mods & Mods.RELAX: f = rx_path / f"{s.id}.osr" elif s.mods & Mods.AUTOPILOT: f = ap_path / f"{s.id}.osr" else: f = vn_path / f"{s.id}.osr" f.write_bytes(replay) if glob.config.anticheat and not s.user.priv & Privileges.BypassAnticheat: loop = asyncio.get_event_loop() loop.create_task(s.analyse()) cap = glob.config.pp_caps[s.mode.value] if (cap is not None and s.pp >= cap and s.map.status & mapStatuses.GIVE_PP and glob.config.anticheat and not s.user.restricted and not s.user.priv & Privileges.Whitelisted): # ugly await s.user.restrict( reason=f"Exceeding PP cap ({s.pp:,}pp) on {s.map.name}", fr=glob.bot, ) # update stats EEEEEEE stats = s.user.stats[s.mode.value] old = copy.copy( stats) # we need a copy of the old stats for submission chart elapsed = mpargs["st" if s.passed else "ft"] if not elapsed and s.user.priv & Privileges.BypassAnticheat: await s.user.restrict( "Modified client", fr=glob.bot, ) # its really only old version, but its supposed to be blocked on login. if it isnt present it must be modified to seem like a new version stats.playtime += int(elapsed) // 1000 stats.tscore += s.score stats.pc += 1 if s.status == scoreStatuses.Best and s.map.status >= mapStatuses.Ranked: add = s.score if s.old_best: add -= s.old_best.score if s.combo > old.max_combo: stats.max_combo = s.combo if s.map.status & mapStatuses.GIVE_PP: stats.rscore += add await s.user.update_stats(s.mode, s.mode.table, s.mode.as_vn) if not s.user.restricted: s.map.plays += 1 if s.passed: s.map.passes += 1 await glob.db.execute( "UPDATE maps SET plays = %s, passes = %s WHERE md5 = %s", [s.map.plays, s.map.passes, s.map.md5], ) # sub charts bruh if s.status == scoreStatuses.Failed: info( f"[{s.mode!r}] {s.user.name} submitted a score on {s.map.name} ({s.status.name})", ) return b"error: no" # not actually erroring, score is already submitted we just want client to stop request as we cannot provide chart achievements = "" if (s.map.status & mapStatuses.GIVE_PP and not s.user.restricted): # TODO: hush-hush etc. achievements achs = [] for ach in glob.achievements: if ach in s.user.achievements: continue if ach.cond(s): await s.user.unlock_ach(ach) achs.append(ach) achievements = "/".join([a.format for a in achs]) charts = [] # could be done better def chart_format(name, b, a): return f'{name}Before:{b or ""}|{name}After:{a}' # osu makes this so ugly man. # map info charts.append( f"beatmapId:{s.map.id}|" f"beatmapSetId:{s.map.sid}|" f"beatmapPlaycount:{s.map.plays}|" f"beatmapPasscount:{s.map.passes}|" f"approvedDate:{s.map.update}", ) # score-specific ranking if s.map.status >= mapStatuses.Ranked: charts.append( "|".join( ( "chartId:beatmap", f"chartUrl:{s.map.set_url}", "chartName:Current Score", *(( # wtaf chart_format("rank", s.old_best.rank, s.rank), chart_format("rankedScore", s.old_best.score, s.score), chart_format("totalScore", s.old_best.score, s.score), chart_format("maxCombo", s.old_best.combo, s.combo), chart_format( "accuracy", round(s.old_best.acc, 2), round(s.acc, 2), ), chart_format("pp", s.old_best.pp, s.pp), ) if s.old_best else ( chart_format("rank", None, s.rank), chart_format("rankedScore", None, s.score), chart_format("totalScore", None, s.score), chart_format("maxCombo", None, s.combo), chart_format("accuracy", None, round(s.acc, 2)), chart_format("pp", None, s.pp), )), f"onlineScoreId:{s.id}", ), ), ) # overall user stats charts.append( "|".join(( "chartId:overall", f"chartUrl:https://{glob.config.domain}/u/{s.user.id}", "chartName:Overall Stats", *(( chart_format("rank", old.rank, stats.rank), chart_format("rankedScore", old.rscore, stats.rscore), chart_format("totalScore", old.tscore, stats.tscore), chart_format("maxCombo", old.max_combo, stats.max_combo), chart_format( "accuracy", round(old.acc, 2), round(stats.acc, 2), ), chart_format("pp", old.pp, stats.pp), ) if old else ( chart_format("rank", None, stats.rank), chart_format("rankedScore", None, stats.rscore), chart_format("totalScore", None, stats.tscore), chart_format("maxCombo", None, stats.max_combo), chart_format("accuracy", None, round(stats.acc, 2)), chart_format("pp", None, stats.pp), )), f"achievements-new:{achievements}", ), ), ) if s.status == scoreStatuses.Best and s.map.status >= mapStatuses.Ranked: if s.rank == 1: # announce #1 to announce channel cus they achieved #1 await s.announce_n1() # update lb cache lb = getattr(s.map, s.mode.leaderboard) if lb: threading.Thread( target=lb.set_user_pb, args=( s.user, s, ), ).start() s.user.last_score = s info( f"[{s.mode!r}] {s.user.name} submitted a score on {s.map.name} ({s.status.name})", ) return "\n".join(charts).encode() # thank u osu