from starlette.responses import Response from starlette.requests import Request from starlette.routing import Route import json import copy import xml.etree.ElementTree as ET from datetime import datetime from config import COIN_REWARD from api.database import player_database, results, decrypt_fields_to_user_info, set_device_data_using_decrypted_fields, results_query, set_user_data_using_decrypted_fields, clear_rank_cache from api.crypt import decrypt_fields from api.template import START_STAGES, EXP_UNLOCKED_SONGS, RESULT_XML from api.misc import should_serve async def score_delta(mode, old_score, new_score): mobile_modes = [1, 2, 3] arcade_modes = [11, 12, 13] if mode in mobile_modes: return new_score - old_score, 0, new_score - old_score elif mode in arcade_modes: return 0, new_score - old_score, new_score - old_score else: return 0, 0, 0 async def result_request(request: Request): decrypted_fields, _ = await decrypt_fields(request) if not decrypted_fields: return Response("""10Invalid request data.""", media_type="application/xml") should_serve_result = await should_serve(decrypted_fields) if not should_serve_result: return Response("""403Access denied.""", media_type="application/xml") device_id = decrypted_fields[b'vid'][0].decode() user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields) tree = copy.deepcopy(RESULT_XML) root = tree.getroot() stts = decrypted_fields[b'stts'][0].decode() song_id = int(decrypted_fields[b'id'][0].decode()) mode = int(decrypted_fields[b'mode'][0].decode()) avatar = int(decrypted_fields[b'avatar'][0].decode()) score = int(decrypted_fields[b'score'][0].decode()) high_score = decrypted_fields[b'high_score'][0].decode() play_rslt = decrypted_fields[b'play_rslt'][0].decode() item = int(decrypted_fields[b'item'][0].decode()) device_os = decrypted_fields[b'os'][0].decode() os_ver = decrypted_fields[b'os_ver'][0].decode() ver = decrypted_fields[b'ver'][0].decode() stts = "[" + stts + "]" high_score = "[" + high_score + "]" play_rslt = "[" + play_rslt + "]" try: stts = json.loads(stts) high_score = json.loads(high_score) play_rslt = json.loads(play_rslt) except: return Response("""10Invalid request data.""", media_type="application/xml") cache_key = f"{song_id}-{mode}" # delete cache with key await clear_rank_cache(cache_key) # Start results processing target_row_id = 0 rank = None user_id = user_info['id'] if user_info else None if user_id: query_param = { "song_id": song_id, "mode": mode, "user_id": user_id } records = await results_query(query_param) mobile_delta, arcade_delta, total_delta = 0, 0, 0 if len(records) != 0: # user row exists target_row_id = records[0]['id'] if score > records[0]['score']: mobile_delta, arcade_delta, total_delta = await score_delta(mode, records[0]['score'], score) update_query = results.update().where(results.c.id == records[0]['id']).values( device_id=device_id, stts=stts, avatar=avatar, score=score, high_score=high_score, play_rslt=play_rslt, item=item, os=device_os, os_ver=os_ver, ver=ver ) await player_database.execute(update_query) else: # insert new row mobile_delta, arcade_delta, total_delta = await score_delta(mode, 0, score) insert_query = results.insert().values( device_id=device_id, user_id=user_id, stts=stts, song_id=song_id, mode=mode, avatar=avatar, score=score, high_score=high_score, play_rslt=play_rslt, item=item, os=device_os, os_ver=os_ver, ver=ver, created_at=datetime.utcnow(), updated_at=datetime.utcnow() ) result = await player_database.execute(insert_query) target_row_id = result # Calculate final rank for client display query_param = { "song_id": song_id, "mode": mode } records = await results_query(query_param) rank = None for idx, record in enumerate(records, start=1): if record["id"] == target_row_id: rank = idx break # Update user score delta if total_delta: update_data = { "mobile_delta": user_info['mobile_delta'] + mobile_delta, "arcade_delta": user_info['arcade_delta'] + arcade_delta, "total_delta": user_info['total_delta'] + total_delta } await set_user_data_using_decrypted_fields(decrypted_fields, update_data) # Unlocking mission stages and updating avatars my_stage = set(device_info["my_stage"]) if device_info and device_info["my_stage"] else set(START_STAGES) current_exp = stts[0] for song in EXP_UNLOCKED_SONGS: if song["lvl"] <= current_exp: my_stage.add(song["id"]) my_stage = sorted(my_stage) update_data = { "lvl": current_exp, "avatar": int(avatar), "my_stage": my_stage } # add coins, skip 4max placeholder songs if int(song_id) not in range(616, 1024) or int(mode) not in range(0, 4): coin_mp = user_info['coin_mp'] if user_info else 1 current_coin = device_info["coin"] if device_info and device_info["coin"] else 0 updated_coin = current_coin + COIN_REWARD * coin_mp update_data["coin"] = updated_coin await set_device_data_using_decrypted_fields(decrypted_fields, update_data) after_element = root.find('.//after') after_element.text = str(rank) xml_response = ET.tostring(tree.getroot(), encoding='unicode') return Response(xml_response, media_type="application/xml") routes = [ Route('/result.php', result_request, methods=['GET']) ]